1053 lines
42 KiB
Lua
1053 lines
42 KiB
Lua
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
|