add sborka

This commit is contained in:
2026-03-31 10:27:04 +03:00
commit f5e5f56c84
2345 changed files with 382127 additions and 0 deletions

View File

@@ -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
})

View File

@@ -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

View File

@@ -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},
}
}

View File

@@ -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")

View File

@@ -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
})