275 lines
10 KiB
Lua
275 lines
10 KiB
Lua
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)
|