Files
VnUtest/garrysmod/gamemodes/militaryrp/plugins/vehicles/sv_plugin.lua
2026-03-31 10:27:04 +03:00

1053 lines
42 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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