add sborka
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
include("shared.lua")
|
||||
|
||||
killicon.Add( "ent_lordi_rocket", "vgui/entities/ent_lordi_rocket", Color( 255, 255, 255, 255 ) )
|
||||
|
||||
--Couldnt be bothered to make a world model... This will hold as a placeholder for now
|
||||
|
||||
function ENT:Initialize()
|
||||
self.modelEnt = ClientsideModel("models/weapons/lordi/c_sledgehammer.mdl", RENDER_GROUP_OPAQUE)
|
||||
|
||||
self.Spin_Ang = 0
|
||||
end
|
||||
|
||||
function ENT:Draw()
|
||||
|
||||
self.Spin_Ang = self.Spin_Ang + 10
|
||||
if (IsValid(self.modelEnt)) and self.modelEnt:LookupBone("sledge_hammer") then
|
||||
self.modelEnt:SetPos(self:GetPos() + self:GetAngles():Right() * 20)
|
||||
self.modelEnt:SetAngles(self:GetAngles())
|
||||
self.modelEnt:SetParent(self)
|
||||
self.modelEnt:ManipulateBoneAngles(self.modelEnt:LookupBone("sledge_hammer"),Angle(174 + self.Spin_Ang,110,87))
|
||||
self.modelEnt:ManipulateBonePosition(self.modelEnt:LookupBone("sledge_hammer"),Vector(-20,27,8.6))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
if (IsValid(self.modelEnt)) then
|
||||
self.modelEnt:Remove()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
AddCSLuaFile("cl_init.lua")
|
||||
AddCSLuaFile("shared.lua")
|
||||
include("shared.lua")
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/weapons/w_crowbar.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
self:SetUseType(SIMPLE_USE)
|
||||
self:EmitSound("npc/zombie/claw_miss1.wav",75,60)
|
||||
local phys = self:GetPhysicsObject()
|
||||
|
||||
if IsValid(phys) then phys:Wake() phys:EnableGravity(false) end
|
||||
|
||||
util.SpriteTrail(self, 0, Color(255,255,255), false, 10, 1, 0.5, 8, "trails/smoke.vmt")
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
if self:GetVelocity():Length() <= 400 then
|
||||
self:PhysicsCollide()
|
||||
return
|
||||
end
|
||||
|
||||
self:EmitSound("weapons/slam/throw.wav")
|
||||
end
|
||||
|
||||
function ENT:Use(activator,caller)
|
||||
end
|
||||
|
||||
hook.Add("EntityTakeDamage","Sledgehammer_Kill",function(ply,dmginfo)
|
||||
if IsValid(dmginfo:GetAttacker()) and dmginfo:GetAttacker():GetClass() and dmginfo:GetAttacker():GetClass() == "ent_lordi_sledgehammer" then
|
||||
if IsValid(dmginfo:GetAttacker():GetOwner()) and dmginfo:GetAttacker():GetOwner():IsPlayer() then
|
||||
dmginfo:SetAttacker(dmginfo:GetAttacker():GetOwner())
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function ENT:Touch( ent )
|
||||
if ent:IsValid() then
|
||||
if ent:IsPlayer() or ent:IsNPC() then
|
||||
for i=1,3 do
|
||||
ent:EmitSound("physics/body/body_medium_break"..math.random(2,4)..".wav",75,math.random(70,90))
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin(ent:GetPos() + Vector(0,0,math.random(5,20)))
|
||||
effectdata:SetEntity(ent)
|
||||
util.Effect( "BloodImpact", effectdata )
|
||||
end
|
||||
ent:TakeDamage(math.random(70,120),self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:PhysicsCollide(data,obj)
|
||||
timer.Create("sledge_hammer_next_frame"..self:EntIndex(),0,1,function()
|
||||
self:EmitSound("physics/metal/metal_barrel_impact_hard7.wav")
|
||||
local sledge = ents.Create("weapon_lordi_sledgehammer")
|
||||
sledge:SetPos(self:GetPos())
|
||||
sledge:SetAngles(self:GetAngles())
|
||||
sledge:Spawn()
|
||||
|
||||
sledge:GetPhysicsObject():ApplyForceCenter( self:GetVelocity() * 15 )
|
||||
|
||||
self:Remove()
|
||||
/*
|
||||
local explode = ents.Create("env_explosion")
|
||||
explode:SetPos( self:GetPos() )
|
||||
explode:SetOwner( self:GetOwner() )
|
||||
explode:Spawn()
|
||||
explode:SetKeyValue("iMagnitude","125")
|
||||
explode:Fire("Explode", 0, 0 )
|
||||
self:EmitSound("ambient/explosions/explode_"..math.random(1,5)..".wav")
|
||||
self:Remove()
|
||||
*/
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.Author = "LordiAnders"
|
||||
ENT.Category = "[FT] Специальное Оружие"
|
||||
ENT.PrintName = "Rocket"
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,319 @@
|
||||
TOOL.Category = "Construction"
|
||||
TOOL.Name = "LED screens"
|
||||
TOOL.Command = nil
|
||||
TOOL.ConfigName = ""
|
||||
|
||||
local TextBox = {}
|
||||
local slider = {}
|
||||
local slider2 = {}
|
||||
local slider3 = {}
|
||||
local frame = {}
|
||||
TOOL.ClientConVar[ "text" ] = ""
|
||||
TOOL.ClientConVar[ "type" ] = 1
|
||||
TOOL.ClientConVar[ "speed" ] = 1.5
|
||||
TOOL.ClientConVar[ "wide" ] = 6
|
||||
TOOL.ClientConVar[ "fx" ] = 0
|
||||
TOOL.ClientConVar[ "wire" ] = 0
|
||||
TOOL.ClientConVar[ "r"] = 255
|
||||
TOOL.ClientConVar[ "g" ] = 200
|
||||
TOOL.ClientConVar[ "b" ] = 0
|
||||
|
||||
if (SERVER) then
|
||||
CreateConVar('sbox_maxledscreens', 5)
|
||||
end
|
||||
|
||||
cleanup.Register("ledscreens")
|
||||
|
||||
TOOL.Information = {
|
||||
{ name = "left" },
|
||||
{ name = "right" }
|
||||
}
|
||||
|
||||
if (CLIENT) then
|
||||
language.Add("Tool.ledscreen.name", "LED screen")
|
||||
language.Add("Tool.ledscreen.desc", "Create a LED panel")
|
||||
|
||||
language.Add("Tool.ledscreen.left", "Spawn a LED panel");
|
||||
language.Add("Tool.ledscreen.right", "Update LED panel");
|
||||
language.Add("Tool.ledscreen.reload", "Copy LED panel's settings");
|
||||
|
||||
language.Add("Undone.ledscreens", "Undone ledscreen")
|
||||
language.Add("Undone_ledscreens", "Undone ledscreen")
|
||||
language.Add("Cleanup.ledscreens", "ledscreens")
|
||||
language.Add("Cleanup_ledscreens", "ledscreens")
|
||||
language.Add("Cleaned.ledscreens", "Cleaned up all ledscreens")
|
||||
language.Add("Cleaned_ledscreens", "Cleaned up all ledscreens")
|
||||
|
||||
language.Add("SBoxLimit.ledscreens", "You've hit the ledscreen limit!")
|
||||
language.Add("SBoxLimit_ledscreens", "You've hit the ledscreen limit!")
|
||||
end
|
||||
|
||||
local function GetConvars(self)
|
||||
local type = tonumber(self:GetClientInfo("type"))
|
||||
if !isnumber(type) then type = 1 end
|
||||
|
||||
local speed = tonumber(self:GetClientInfo("speed"))
|
||||
if !isnumber(speed) then speed = 1.5 end
|
||||
|
||||
local wide = tonumber(self:GetClientInfo("wide"))
|
||||
if !isnumber(wide) then wide = 6 end
|
||||
|
||||
local fx = tonumber(self:GetClientInfo("fx"))
|
||||
if !isnumber(fx) then fx = 0 end
|
||||
|
||||
local r, g, b = tonumber(self:GetClientInfo("r")), tonumber(self:GetClientInfo("g")), tonumber(self:GetClientInfo("b"))
|
||||
if !isnumber(r) or !isnumber(g) or !isnumber(b) then r, g, b = 255, 200, 100 end
|
||||
|
||||
return math.Clamp(type, 1, 4), math.Clamp(speed, 1, 10), math.Clamp(wide, 3, 8), math.Clamp(fx, 0, 1), r, g, b
|
||||
end
|
||||
|
||||
function TOOL:LeftClick(tr)
|
||||
if (tr.Entity:GetClass() == "player") then return false end
|
||||
if (CLIENT) then return true end
|
||||
|
||||
local Ply = self:GetOwner()
|
||||
local centerpos = {
|
||||
[3] = {18, 0},
|
||||
[4] = {11.5, 6},
|
||||
[5] = {18, 6},
|
||||
[6] = {36, 6},
|
||||
[7] = {42, 6},
|
||||
[8] = {48, 6},
|
||||
}
|
||||
|
||||
local type, speed, wide, fx, r, g, b = GetConvars(self)
|
||||
|
||||
local angle = tr.HitNormal:Angle()
|
||||
local SpawnPos = tr.HitPos + angle:Right() * centerpos[wide][1] - angle:Up() * centerpos[wide][2]
|
||||
|
||||
if not (self:GetWeapon():CheckLimit("ledscreens")) then return false end
|
||||
|
||||
local TextScreen
|
||||
if tonumber(self:GetClientInfo("wire")) > 0 then
|
||||
TextScreen = ents.Create("gb_rp_sign_wire")
|
||||
else
|
||||
TextScreen = ents.Create("gb_rp_sign")
|
||||
end
|
||||
TextScreen:SetPos(SpawnPos)
|
||||
TextScreen:Spawn()
|
||||
|
||||
angle:RotateAroundAxis(tr.HitNormal:Angle():Right(), -90)
|
||||
angle:RotateAroundAxis(tr.HitNormal:Angle():Forward(), 90)
|
||||
TextScreen:SetAngles(angle)
|
||||
TextScreen:SetText(self:GetClientInfo("text"))
|
||||
TextScreen:SetType(type)
|
||||
TextScreen:SetSpeed(speed)
|
||||
TextScreen:SetWide(wide)
|
||||
TextScreen:SetModel("models/squad/sf_plates/sf_plate1x"..wide..".mdl")
|
||||
TextScreen:SetTColor(Vector(r/100, g/100, b/100))
|
||||
TextScreen:SetFX(fx)
|
||||
TextScreen:Activate()
|
||||
local Phys = TextScreen:GetPhysicsObject()
|
||||
Phys:EnableMotion( false )
|
||||
|
||||
undo.Create("ledscreens")
|
||||
|
||||
undo.AddEntity(TextScreen)
|
||||
undo.SetPlayer(Ply)
|
||||
undo.Finish()
|
||||
|
||||
Ply:AddCount("ledscreens", TextScreen)
|
||||
Ply:AddCleanup("ledscreens", TextScreen)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function TOOL:RightClick(tr)
|
||||
if (tr.Entity:GetClass() == "player") then return false end
|
||||
if (CLIENT) then return true end
|
||||
|
||||
local TraceEnt = tr.Entity
|
||||
|
||||
local type, speed, wide, fx, r, g, b = GetConvars(self)
|
||||
|
||||
if (TraceEnt:IsValid() and TraceEnt:GetClass() == "gb_rp_sign") then
|
||||
TraceEnt:SetText(self:GetClientInfo("text"))
|
||||
TraceEnt:SetType(type)
|
||||
TraceEnt:SetSpeed(speed)
|
||||
TraceEnt:SetWide(wide)
|
||||
TraceEnt:SetFX(fx)
|
||||
TraceEnt:SetModel("models/squad/sf_plates/sf_plate1x"..wide..".mdl")
|
||||
TraceEnt:SetTColor(Vector(r/100, g/100, b/100))
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
function TOOL:Reload(tr)
|
||||
|
||||
if !IsValid(tr.Entity) then return false end
|
||||
local TraceEnt = tr.Entity
|
||||
|
||||
if (TraceEnt:IsValid() and TraceEnt:GetClass() == "gb_rp_sign") then
|
||||
if CLIENT or game.SinglePlayer() then
|
||||
local color = TraceEnt:GetTColor()
|
||||
RunConsoleCommand("ledscreen_text", TraceEnt:GetText())
|
||||
RunConsoleCommand("ledscreen_type", TraceEnt:GetType())
|
||||
RunConsoleCommand("ledscreen_r", color.x*100)
|
||||
RunConsoleCommand("ledscreen_g", color.y*100)
|
||||
RunConsoleCommand("ledscreen_b", color.z*100)
|
||||
RunConsoleCommand("ledscreen_speed", TraceEnt:GetSpeed())
|
||||
RunConsoleCommand("ledscreen_wide", TraceEnt:GetWide())
|
||||
RunConsoleCommand("ledscreen_fx", TraceEnt:GetFX())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
|
||||
function TOOL.BuildCPanel(CPanel)
|
||||
CPanel:AddControl("Header", { Text = "#Tool.ledscreen.name" } )
|
||||
resetall = vgui.Create("DButton", resetbuttons)
|
||||
resetall:SetSize(100, 25)
|
||||
resetall:SetText("Reset all")
|
||||
resetall.DoClick = function()
|
||||
RunConsoleCommand("ledscreen_text", "")
|
||||
RunConsoleCommand("ledscreen_type", 1)
|
||||
RunConsoleCommand("ledscreen_r", 255)
|
||||
RunConsoleCommand("ledscreen_g", 200)
|
||||
RunConsoleCommand("ledscreen_b", 0)
|
||||
RunConsoleCommand("ledscreen_speed", 1.5)
|
||||
RunConsoleCommand("ledscreen_wide", 6)
|
||||
RunConsoleCommand("ledscreen_fx", 0)
|
||||
RunConsoleCommand("ledscreen_wire", 0)
|
||||
slider:SetValue(1)
|
||||
slider2:SetValue(1.5)
|
||||
slider3:SetValue(6)
|
||||
TextBox:SetValue("")
|
||||
|
||||
end
|
||||
CPanel:AddItem(resetall)
|
||||
local font = "InfoRUS3"
|
||||
frame = vgui.Create( "DPanel" )
|
||||
frame:SetSize( CPanel:GetWide(), 50 )
|
||||
frame.appr = nil
|
||||
frame.Paint = function(self,w,h)
|
||||
draw.RoundedBox(0,0,0,w,h,Color(0,0,0))
|
||||
surface.SetFont(font)
|
||||
local alfa
|
||||
if GetConVarNumber("ledscreen_fx") > 0 then
|
||||
alfa = math.random(100,220)
|
||||
else
|
||||
alfa = 255
|
||||
end
|
||||
self.Text = GetConVarString("ledscreen_text")
|
||||
self.Type = GetConVarNumber("ledscreen_type")
|
||||
self.Speed = GetConVarNumber("ledscreen_speed")
|
||||
self.static = false
|
||||
local ww,hh = surface.GetTextSize(self.Text)
|
||||
local multiplier = self.Speed * 100
|
||||
self.Color = Color(GetConVarNumber("ledscreen_r"),GetConVarNumber("ledscreen_g"),GetConVarNumber("ledscreen_b"), alfa)
|
||||
if self.Type == 1 then
|
||||
|
||||
local xs = (math.fmod(SysTime() * multiplier,w+ww)) - ww
|
||||
draw.DrawText(self.Text,font,xs,0,self.Color,0)
|
||||
|
||||
elseif self.Type == 2 then
|
||||
|
||||
if !self.appr or self.appr > ww then
|
||||
self.appr = -w
|
||||
else
|
||||
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
|
||||
end
|
||||
|
||||
draw.DrawText(self.Text,font,self.appr * -1,0,self.Color,0)
|
||||
|
||||
else
|
||||
if !self.appr then
|
||||
self.appr = 0
|
||||
end
|
||||
|
||||
if w > ww then
|
||||
if self.Type == 3 then
|
||||
if self.appr < w-ww and !self.refl then
|
||||
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
|
||||
else
|
||||
if self.appr <= 0 then
|
||||
self.refl = nil
|
||||
else
|
||||
self.refl = true
|
||||
self.appr = math.Approach(self.appr, 0, FrameTime() * multiplier)
|
||||
end
|
||||
end
|
||||
else
|
||||
self.static = true
|
||||
end
|
||||
else
|
||||
if self.appr > w-ww-50 and !self.refl then
|
||||
self.appr = math.Approach(self.appr, w-ww-50, FrameTime() * multiplier)
|
||||
else
|
||||
if self.appr >= 50 then
|
||||
self.refl = nil
|
||||
else
|
||||
self.refl = true
|
||||
self.appr = math.Approach(self.appr, 50, FrameTime() * multiplier)
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.static then
|
||||
draw.DrawText(self.Text,font,w/2,0,self.Color,1)
|
||||
else
|
||||
draw.DrawText(self.Text,font,self.appr,0,self.Color,0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
CPanel:AddItem(frame)
|
||||
|
||||
|
||||
slider = vgui.Create("DNumSlider")
|
||||
slider:SetText("Type")
|
||||
slider:SetMinMax(1, 4)
|
||||
slider:SetDecimals(0)
|
||||
slider:SetValue(1)
|
||||
slider:SetConVar("ledscreen_type")
|
||||
CPanel:AddItem(slider)
|
||||
|
||||
slider2 = vgui.Create("DNumSlider")
|
||||
slider2:SetText("Speed")
|
||||
slider2:SetMinMax(1, 10)
|
||||
slider2:SetDecimals(1)
|
||||
slider2:SetValue(1)
|
||||
slider2:SetConVar("ledscreen_speed")
|
||||
CPanel:AddItem(slider2)
|
||||
|
||||
slider3 = vgui.Create("DNumSlider")
|
||||
slider3:SetText("Wide")
|
||||
slider3:SetMinMax(3, 8)
|
||||
slider3:SetDecimals(0)
|
||||
slider3:SetValue(6)
|
||||
slider3:SetConVar("ledscreen_wide")
|
||||
CPanel:AddItem(slider3)
|
||||
|
||||
TextBox = vgui.Create("DTextEntry")
|
||||
TextBox:SetUpdateOnType(true)
|
||||
TextBox:SetEnterAllowed(true)
|
||||
TextBox:SetConVar("ledscreen_text")
|
||||
TextBox:SetValue(GetConVarString("ledscreen_text"))
|
||||
CPanel:AddItem(TextBox)
|
||||
|
||||
CPanel:AddControl( "CheckBox", { Label = "Flicker effect", Description = "", Command = "ledscreen_fx" } )
|
||||
CPanel:AddControl( "CheckBox", { Label = "WireMod support", Description = "", Command = "ledscreen_wire" } )
|
||||
|
||||
CPanel:AddControl("Color", {
|
||||
Label = "LED color",
|
||||
Red = "ledscreen_r",
|
||||
Green = "ledscreen_g",
|
||||
Blue = "ledscreen_b",
|
||||
ShowHSV = 1,
|
||||
ShowRGB = 1,
|
||||
Multiplier = 255
|
||||
})
|
||||
|
||||
CPanel:AddControl("Label", { Text = "Gmod-Best.Ru ©2013-2019\nWith <3 from Mac" } )
|
||||
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
PermaProps
|
||||
Created by Entoros, June 2010
|
||||
Facepunch: http://www.facepunch.com/member.php?u=180808
|
||||
Modified By Malboro 28 / 12 / 2012
|
||||
|
||||
Ideas:
|
||||
Make permaprops cleanup-able
|
||||
|
||||
Errors:
|
||||
Errors on die
|
||||
|
||||
Remake:
|
||||
By Malboro the 28/12/2012
|
||||
*/
|
||||
|
||||
TOOL.Category = "Props Tool"
|
||||
TOOL.Name = "PermaProps"
|
||||
TOOL.Command = nil
|
||||
TOOL.ConfigName = ""
|
||||
|
||||
if CLIENT then
|
||||
language.Add("Tool.permaprops.name", "PermaProps")
|
||||
language.Add("Tool.permaprops.desc", "Save a props permanently")
|
||||
language.Add("Tool.permaprops.0", "LeftClick: Add RightClick: Remove Reload: Update")
|
||||
|
||||
surface.CreateFont("PermaPropsToolScreenFont", { font = "Arial", size = 40, weight = 1000, antialias = true, additive = false })
|
||||
surface.CreateFont("PermaPropsToolScreenSubFont", { font = "Arial", size = 30, weight = 1000, antialias = true, additive = false })
|
||||
end
|
||||
|
||||
function TOOL:LeftClick(trace)
|
||||
|
||||
if CLIENT then return true end
|
||||
|
||||
local ent = trace.Entity
|
||||
local ply = self:GetOwner()
|
||||
|
||||
if not PermaProps then ply:ChatPrint( "ERROR: Lib not found" ) return end
|
||||
|
||||
if !PermaProps.HasPermission( ply, "Save") then return end
|
||||
|
||||
if not ent:IsValid() then ply:ChatPrint( "That is not a valid entity !" ) return end
|
||||
if ent:IsPlayer() then ply:ChatPrint( "That is a player !" ) return end
|
||||
if ent.PermaProps then ply:ChatPrint( "That entity is already permanent !" ) return end
|
||||
|
||||
local content = PermaProps.PPGetEntTable(ent)
|
||||
if not content then return end
|
||||
|
||||
local max = tonumber(sql.QueryValue("SELECT MAX(id) FROM permaprops;"))
|
||||
if not max then max = 1 else max = max + 1 end
|
||||
|
||||
local new_ent = PermaProps.PPEntityFromTable(content, max)
|
||||
if !new_ent or !new_ent:IsValid() then return end
|
||||
|
||||
PermaProps.SparksEffect( ent )
|
||||
|
||||
PermaProps.SQL.Query("INSERT INTO permaprops (id, map, content) VALUES(NULL, ".. sql.SQLStr(game.GetMap()) ..", ".. sql.SQLStr(util.TableToJSON(content)) ..");")
|
||||
ply:ChatPrint("You saved " .. ent:GetClass() .. " with model ".. ent:GetModel() .. " to the database.")
|
||||
|
||||
ent:Remove()
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
function TOOL:RightClick(trace)
|
||||
|
||||
if CLIENT then return true end
|
||||
|
||||
local ent = trace.Entity
|
||||
local ply = self:GetOwner()
|
||||
|
||||
if not PermaProps then ply:ChatPrint( "ERROR: Lib not found" ) return end
|
||||
|
||||
if !PermaProps.HasPermission( ply, "Delete") then return end
|
||||
|
||||
if not ent:IsValid() then ply:ChatPrint( "That is not a valid entity !" ) return end
|
||||
if ent:IsPlayer() then ply:ChatPrint( "That is a player !" ) return end
|
||||
if not ent.PermaProps then ply:ChatPrint( "That is not a PermaProp !" ) return end
|
||||
if not ent.PermaProps_ID then ply:ChatPrint( "ERROR: ID not found" ) return end
|
||||
|
||||
PermaProps.SQL.Query("DELETE FROM permaprops WHERE id = ".. ent.PermaProps_ID ..";")
|
||||
|
||||
ply:ChatPrint("You erased " .. ent:GetClass() .. " with a model of " .. ent:GetModel() .. " from the database.")
|
||||
|
||||
ent:Remove()
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
function TOOL:Reload(trace)
|
||||
|
||||
if CLIENT then return true end
|
||||
|
||||
if not PermaProps then self:GetOwner():ChatPrint( "ERROR: Lib not found" ) return end
|
||||
|
||||
if (not trace.Entity:IsValid() and PermaProps.HasPermission( self:GetOwner(), "Update")) then self:GetOwner():ChatPrint( "You have reload all PermaProps !" ) PermaProps.ReloadPermaProps() return false end
|
||||
|
||||
if trace.Entity.PermaProps then
|
||||
|
||||
local ent = trace.Entity
|
||||
local ply = self:GetOwner()
|
||||
|
||||
if !PermaProps.HasPermission( ply, "Update") then return end
|
||||
|
||||
if ent:IsPlayer() then ply:ChatPrint( "That is a player !" ) return end
|
||||
|
||||
local content = PermaProps.PPGetEntTable(ent)
|
||||
if not content then return end
|
||||
|
||||
PermaProps.SQL.Query("UPDATE permaprops set content = ".. sql.SQLStr(util.TableToJSON(content)) .." WHERE id = ".. ent.PermaProps_ID .." AND map = ".. sql.SQLStr(game.GetMap()) .. ";")
|
||||
|
||||
local new_ent = PermaProps.PPEntityFromTable(content, ent.PermaProps_ID)
|
||||
if !new_ent or !new_ent:IsValid() then return end
|
||||
|
||||
PermaProps.SparksEffect( ent )
|
||||
|
||||
ply:ChatPrint("You updated the " .. ent:GetClass() .. " in the database.")
|
||||
|
||||
ent:Remove()
|
||||
|
||||
|
||||
else
|
||||
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
function TOOL.BuildCPanel(panel)
|
||||
|
||||
panel:AddControl("Header",{Text = "PermaProps", Description = "PermaProps\n\nSaves entities across map changes\n"})
|
||||
panel:AddControl("Button",{Label = "Open Configuration Menu", Command = "pp_cfg_open"})
|
||||
|
||||
end
|
||||
|
||||
function TOOL:DrawToolScreen(width, height)
|
||||
|
||||
if SERVER then return end
|
||||
|
||||
surface.SetDrawColor(17, 148, 240, 255)
|
||||
surface.DrawRect(0, 0, 256, 256)
|
||||
|
||||
surface.SetFont("PermaPropsToolScreenFont")
|
||||
local w, h = surface.GetTextSize(" ")
|
||||
surface.SetFont("PermaPropsToolScreenSubFont")
|
||||
local w2, h2 = surface.GetTextSize(" ")
|
||||
|
||||
draw.SimpleText("PermaProps", "PermaPropsToolScreenFont", 128, 100, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(17, 148, 240, 255), 4)
|
||||
draw.SimpleText("By Malboro", "PermaPropsToolScreenSubFont", 128, 128 + (h + h2) / 2 - 4, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(17, 148, 240, 255), 4)
|
||||
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,285 @@
|
||||
if ( SERVER ) then
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
|
||||
util.AddNetworkString("anterg0_guitarTab")
|
||||
util.AddNetworkString("anterg0_guitarPlaySong")
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
SWEP.DrawCrosshair = false
|
||||
SWEP.PrintName = "Русская гитара"
|
||||
end
|
||||
|
||||
SWEP.Base = "weapon_base"
|
||||
|
||||
SWEP.PrintName = "Русская гитара"
|
||||
SWEP.Slot = 2
|
||||
SWEP.SlotPos = 4
|
||||
SWEP.DrawAmmo = false
|
||||
SWEP.ViewModel = "models/weapons/tayley/v_guitar.mdl"
|
||||
SWEP.HoldType = "slam"
|
||||
SWEP.ViewModelFOV = 70
|
||||
SWEP.ViewModelFlip = false
|
||||
SWEP.WorldModel = "models/anterg0_Guitar/marauder_guitar.mdl"
|
||||
-- Other settings
|
||||
SWEP.Weight = 5
|
||||
SWEP.AutoSwitchTo = false
|
||||
SWEP.AutoSwitchFrom = false
|
||||
SWEP.Spawnable = true
|
||||
SWEP.AdminSpawnable = true
|
||||
|
||||
-- SWEP info
|
||||
SWEP.Author = "DuckFlit"
|
||||
SWEP.Category = "[FT] Атмосфера"
|
||||
SWEP.Instructions = "Левый клик дабы играть случайную песню. Правый клик чтобы выбрать песню."
|
||||
|
||||
-- Primary fire settings
|
||||
SWEP.Primary.Damage = -1
|
||||
SWEP.Primary.NumShots = -1
|
||||
SWEP.Primary.Delay = 2
|
||||
SWEP.Primary.ClipSize = -1
|
||||
SWEP.Primary.DefaultClip = 0
|
||||
SWEP.Primary.Tracer = -1
|
||||
SWEP.Primary.Force = -1
|
||||
SWEP.Primary.TakeAmmoPerBullet = false
|
||||
SWEP.Primary.Automatic = false
|
||||
SWEP.Primary.Ammo = "none"
|
||||
|
||||
SWEP.ReloadTimer = 1
|
||||
|
||||
local songsTbl = {
|
||||
|
||||
[1] = {"Спецназ - Денис Майданов", "weapons/guitar/ad.mp3"},
|
||||
[2] = {"Мужество Ведёт на Небеса", "weapons/guitar/ac.mp3"},
|
||||
[3] = {"Витязи", "weapons/guitar/ag.mp3"},
|
||||
[4] = {"Чёрный Октябрь ", "weapons/guitar/aj.mp3"},
|
||||
[5] = {"За что мы пьём", "weapons/guitar/ak.mp3"},
|
||||
[6] = {"Набирает высоту", "weapons/guitar/ar.mp3"},
|
||||
[7] = {"Стрим - Песня Бритвы", "weapons/guitar/ay.mp3"},
|
||||
[8] = {"Простите меня", "weapons/guitar/am.mp3"},
|
||||
[9] = {"Владимир Высоцкий - Солдаты Группы", "weapons/guitar/ap.mp3"},
|
||||
[10] = {"ГСН-Российскийспецназ", "weapons/guitar/fa.mp3"},
|
||||
[11] = {"Егор Авдеев - 333 с ночи до зари", "weapons/guitar/fr.mp3"},
|
||||
[12] = {"Николай Анисимов - По самому прямому назначению", "weapons/guitar/fy.mp3"},
|
||||
[13] = {"UKRAINE - Гімн Українскої Артилерії", "weapons/guitar/jj.mp3"},
|
||||
}
|
||||
|
||||
if CLIENT then
|
||||
|
||||
SWEP.WepSelectIcon = surface.GetTextureID("vgui/selection/guitar_ico")
|
||||
function SWEP:DrawWeaponSelection(x, y, wide, tall, alpha)
|
||||
x = x + wide / 2
|
||||
y = y + tall / 2
|
||||
|
||||
tall = tall * 0.75
|
||||
|
||||
x = x - tall / 2
|
||||
y = y - tall / 2 - 10
|
||||
|
||||
surface.SetDrawColor(255, 255, 255, alpha)
|
||||
surface.SetTexture(self.WepSelectIcon)
|
||||
|
||||
surface.DrawTexturedRect(x, y, tall, tall)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SWEP:Initialize()
|
||||
self:SetWeaponHoldType(self.HoldType)
|
||||
end
|
||||
|
||||
function SWEP:Holster()
|
||||
self.Weapon:EmitSound(Sound("weapons/guitar/squak2.mp3"))
|
||||
return true
|
||||
end
|
||||
|
||||
function SWEP:OnRemove()
|
||||
self:Holster()
|
||||
end
|
||||
|
||||
function SWEP:PrimaryAttack()
|
||||
self:SetNextPrimaryFire(CurTime() + self.Primary.Delay);
|
||||
self.Weapon:EmitSound(Sound("weapons/guitar/squak2.mp3"), 50, math.random(85,100), 1, CHAN_SWEP)
|
||||
|
||||
self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK )
|
||||
self.Owner:SetAnimation( PLAYER_ATTACK1 )
|
||||
if SERVER then
|
||||
local songChoice = math.random(1, #songsTbl)
|
||||
self.Weapon:SetNWBool("guitar_isPlaying", true)
|
||||
self.Weapon:SetNWInt("guitar_songNum", songChoice)
|
||||
end
|
||||
timer.Simple(0.5, function()
|
||||
local song = songsTbl[self.Weapon:GetNWInt("guitar_songNum")][2]
|
||||
if(song == nil) then
|
||||
song = ""
|
||||
end
|
||||
self.Weapon:EmitSound(song, 80, 100, 1, CHAN_SWEP)
|
||||
end)
|
||||
end
|
||||
|
||||
function SWEP:SecondaryAttack()
|
||||
local ply = self.Owner
|
||||
if SERVER then
|
||||
net.Start("anterg0_guitarTab")
|
||||
net.Send(ply)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function SWEP:Think()
|
||||
end
|
||||
|
||||
function SWEP:Reload()
|
||||
if(self.Weapon:GetNextSecondaryFire() <= CurTime() ) then
|
||||
self.Weapon:SetNextSecondaryFire(CurTime() + self.ReloadTimer)
|
||||
self.Weapon:SetNWBool("guitar_isPlaying", false)
|
||||
self.Weapon:EmitSound(Sound("weapons/guitar/squak2.mp3"), 50, math.random(85,100), 1, CHAN_SWEP)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:Deploy()
|
||||
self:SetWeaponHoldType(self.HoldType)
|
||||
return true
|
||||
end
|
||||
|
||||
SWEP.RenderGroup = RENDERGROUP_BOTH
|
||||
|
||||
function SWEP:DrawWorldModelTranslucent()
|
||||
self:DrawModel()
|
||||
|
||||
if self.time == nil then self.time = 0 end
|
||||
surface.SetFont("Trebuchet24")
|
||||
local tag = "Now playing ... "
|
||||
if(self.Weapon:GetNWInt("guitar_songNum") ~= 0) then
|
||||
songName = songsTbl[self.Weapon:GetNWInt("guitar_songNum")][1]
|
||||
tag = "Now playing ... " .. songName
|
||||
end
|
||||
local textwidth = surface.GetTextSize(tag)
|
||||
|
||||
if LocalPlayer():GetPos():Distance(self:GetPos()) < 550 then
|
||||
if(self.Weapon:GetNWBool("guitar_isPlaying") == false) then return end
|
||||
local alpha = (LocalPlayer():GetPos():Distance(self:GetPos()) / 100.0)
|
||||
alpha = math.Clamp(2.5 - alpha, 0 ,1)
|
||||
local a = Angle(0,0,0)
|
||||
a:RotateAroundAxis(Vector(1,0,0),90)
|
||||
a.y = LocalPlayer():GetAngles().y - 90
|
||||
cam.Start3D2D(self:GetPos() + Vector(0,0,45 + (math.sin(self.time*0.5)*2)), a , 0.15)
|
||||
draw.SimpleText(tag,"Trebuchet24", -textwidth*0.5,0,Color(205,225,255,255*alpha) , 0 , 1)
|
||||
cam.End3D2D()
|
||||
end
|
||||
|
||||
self.time = self.time + FrameTime()
|
||||
end
|
||||
|
||||
net.Receive("anterg0_guitarTab", function(len, ply)
|
||||
local songNum = net.ReadFloat()
|
||||
|
||||
local wep = ply:GetActiveWeapon()
|
||||
if(wep:GetClass() == "guitar") then
|
||||
wep:SetNWBool("guitar_isPlaying", true)
|
||||
wep:SetNWInt("guitar_songNum", songNum)
|
||||
wep:EmitSound( songsTbl[songNum][2], 80, 100, 1, CHAN_SWEP)
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
// DERMA STUFF
|
||||
|
||||
if CLIENT then
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
local w = ScrW()*0.3
|
||||
local h = ScrH()*0.4
|
||||
self:SetSize(w, h)
|
||||
self:SetPos(ScrW()*0.7, ScrH()/2 - (h/2))
|
||||
|
||||
self:MakePopup()
|
||||
self:SetDeleteOnClose(true)
|
||||
self:SetDraggable(true)
|
||||
self:ShowCloseButton(true)
|
||||
self:SetTitle("")
|
||||
|
||||
self.Paint = function(_, w, h)
|
||||
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 225))
|
||||
end
|
||||
|
||||
self.MusicOptions = vgui.Create("anterg0_Guitar:DScroll", self)
|
||||
|
||||
end
|
||||
|
||||
vgui.Register("anterg0_Guitar:Menu", PANEL, "DFrame")
|
||||
|
||||
net.Receive("anterg0_guitarTab", function()
|
||||
if(IsValid(guitPanel)) then return end
|
||||
guitPanel = vgui.Create("anterg0_Guitar:Menu")
|
||||
end)
|
||||
|
||||
// MUSIC
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
local x,y = self:GetParent():GetSize()
|
||||
self:SetSize(x, y + 60)
|
||||
self:SetPos(0, 30)
|
||||
|
||||
--Modify Scroll Bar--
|
||||
local sbar = self:GetVBar()
|
||||
sbar:SetHideButtons(true)
|
||||
sbar.Paint = function(_, w, h)
|
||||
draw.RoundedBox(0, 0, 0, (w/2), h, Color(0, 0, 0, 225))
|
||||
end
|
||||
|
||||
sbar.btnGrip.Paint = function(_, w, h)
|
||||
draw.RoundedBox(0, 0, 0, (w/2), h, Color(255, 255, 255, 255))
|
||||
end
|
||||
|
||||
self.Paint = function(_, w, h)
|
||||
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 0))
|
||||
end
|
||||
|
||||
for k, v in ipairs(songsTbl) do
|
||||
local btn = self:Add("DButton")
|
||||
btn:Dock(TOP)
|
||||
btn:DockMargin(10, 0, 10, 10)
|
||||
btn:SetSize(0, 25)
|
||||
btn:SetText("")
|
||||
btn.HoverLerp = 0
|
||||
btn.Paint = function(_, w, h)
|
||||
if btn:IsHovered() then
|
||||
btn.HoverLerp = Lerp( FrameTime() * 10, btn.HoverLerp, 1 )
|
||||
else
|
||||
btn.HoverLerp = Lerp( FrameTime() * 3, btn.HoverLerp, 0.1)
|
||||
end
|
||||
draw.RoundedBox(0, 0, 0, w, h, Color(50,50,50,255*btn.HoverLerp))
|
||||
draw.SimpleText(v[1], "Trebuchet18", w/2, 2, Color(255,255,255, 255), 1, 0)
|
||||
end
|
||||
|
||||
btn.PaintOver = function(_, w, h)
|
||||
surface.SetDrawColor(Color(255, 255, 255, 125), 1, 1)
|
||||
surface.DrawOutlinedRect(0, 0, w, h)
|
||||
end
|
||||
|
||||
btn.DoClick = function()
|
||||
local wep = LocalPlayer():GetActiveWeapon()
|
||||
if(wep ~= nil or IsValid(wep)) then
|
||||
LocalPlayer():EmitSound(Sound("weapons/guitar/squak2.mp3") )
|
||||
wep:EmitSound( v[2], 100, 100, 1, CHAN_SWEP )
|
||||
end
|
||||
net.Start("anterg0_guitarTab")
|
||||
net.WriteFloat(k)
|
||||
net.SendToServer()
|
||||
|
||||
self:GetParent():Close()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:Dock(FILL)
|
||||
self:DockMargin(0, 0, 0, 10)
|
||||
end
|
||||
|
||||
vgui.Register("anterg0_Guitar:DScroll", PANEL, "DScrollPanel")
|
||||
end
|
||||
@@ -0,0 +1,195 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
SWEP.Base = "weapon_base"
|
||||
|
||||
if CLIENT then
|
||||
SWEP.DrawWeaponInfoBox = false
|
||||
SWEP.BounceWeaponIcon = false
|
||||
end
|
||||
|
||||
SWEP.PrintName = "Consumable"
|
||||
SWEP.Author = "Helix"
|
||||
SWEP.Purpose = "Base consumable SWEP with animations"
|
||||
SWEP.Category = "Helix"
|
||||
|
||||
SWEP.ViewModelFOV = 70
|
||||
SWEP.ViewModel = "models/weapons/v_hands.mdl"
|
||||
SWEP.WorldModel = ""
|
||||
SWEP.UseHands = true
|
||||
SWEP.DrawCrosshair = false
|
||||
|
||||
SWEP.Spawnable = false
|
||||
SWEP.AutoSwitchTo = false
|
||||
SWEP.AutoSwitchFrom = false
|
||||
SWEP.Slot = 5
|
||||
SWEP.SlotPos = 0
|
||||
|
||||
SWEP.SwayScale = 0
|
||||
SWEP.BobScale = 0
|
||||
|
||||
SWEP.Primary.ClipSize = -1
|
||||
SWEP.Primary.DefaultClip = -1
|
||||
SWEP.Primary.Automatic = false
|
||||
SWEP.Primary.Ammo = "none"
|
||||
|
||||
SWEP.Secondary.ClipSize = -1
|
||||
SWEP.Secondary.DefaultClip = -1
|
||||
SWEP.Secondary.Automatic = false
|
||||
SWEP.Secondary.Ammo = "none"
|
||||
|
||||
-- Параметры предмета (устанавливаются при выдаче)
|
||||
SWEP.ItemHunger = 0
|
||||
SWEP.ItemThirst = 0
|
||||
SWEP.ItemAlcohol = 0
|
||||
SWEP.ItemEmpty = false
|
||||
SWEP.ItemType = "food" -- "food", "drink", "consumable"
|
||||
|
||||
function SWEP:Initialize()
|
||||
self:SetHoldType("slam")
|
||||
self.Consuming = false
|
||||
end
|
||||
|
||||
function SWEP:SetItemData(hunger, thirst, alcohol, empty, itemType, viewModel)
|
||||
self.ItemHunger = hunger or 0
|
||||
self.ItemThirst = thirst or 0
|
||||
self.ItemAlcohol = alcohol or 0
|
||||
self.ItemEmpty = empty or false
|
||||
self.ItemType = itemType or "food"
|
||||
|
||||
-- Устанавливаем viewmodel, если указана
|
||||
if viewModel and viewModel != "" then
|
||||
self.ViewModel = viewModel
|
||||
end
|
||||
print(self.ViewModel)
|
||||
end
|
||||
|
||||
function SWEP:Deploy()
|
||||
local owner = self:GetOwner()
|
||||
|
||||
if not IsValid(owner) or not owner:IsPlayer() then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Проигрываем idle анимацию
|
||||
local vm = owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
local idleSeq = vm:LookupSequence("idle")
|
||||
if idleSeq > 0 then
|
||||
vm:SendViewModelMatchingSequence(idleSeq)
|
||||
end
|
||||
end
|
||||
|
||||
-- Начинаем процесс употребления
|
||||
self.Consuming = true
|
||||
self:InitializeConsumable()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function SWEP:InitializeConsumable()
|
||||
local owner = self:GetOwner()
|
||||
if not IsValid(self) or not IsValid(owner) or not owner:IsPlayer() then return end
|
||||
|
||||
-- Проигрываем анимацию use
|
||||
local vm = owner:GetViewModel()
|
||||
local sequenceDuration = 3
|
||||
|
||||
if IsValid(vm) then
|
||||
local useSeq = vm:LookupSequence("use")
|
||||
if useSeq > 0 then
|
||||
vm:SendViewModelMatchingSequence(useSeq)
|
||||
sequenceDuration = vm:SequenceDuration(useSeq)
|
||||
end
|
||||
end
|
||||
|
||||
-- Применяем эффекты в середине анимации
|
||||
timer.Simple(sequenceDuration * 0.5, function()
|
||||
if IsValid(self) and IsValid(owner) and owner:Alive() then
|
||||
self:ApplyEffects(owner)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Завершаем употребление и удаляем оружие
|
||||
timer.Simple(sequenceDuration, function()
|
||||
if IsValid(self) and IsValid(owner) and owner:Alive() then
|
||||
self.Consuming = false
|
||||
|
||||
if SERVER then
|
||||
owner:StripWeapon(self:GetClass())
|
||||
|
||||
local prev = owner:GetPreviousWeapon() or ""
|
||||
if isentity(prev) and IsValid(prev) then
|
||||
owner:SelectWeapon(prev:GetClass())
|
||||
elseif isstring(prev) and prev != "" then
|
||||
owner:SelectWeapon(prev)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function SWEP:ApplyEffects(owner)
|
||||
if not SERVER then return end
|
||||
if not IsValid(owner) then return end
|
||||
|
||||
local char = owner:GetCharacter()
|
||||
if not char then return end
|
||||
|
||||
-- Получаем текущие значения
|
||||
local hunger = char:GetData("hunger", 100)
|
||||
local thirst = char:GetData("thirst", 100)
|
||||
|
||||
-- Применяем изменения (SetHunger и SetThirst автоматически обновляют состояние)
|
||||
owner:SetHunger(hunger + self.ItemHunger)
|
||||
owner:SetThirst(thirst + self.ItemThirst)
|
||||
|
||||
-- Алкоголь
|
||||
if self.ItemAlcohol > 0 then
|
||||
owner:IncreaseDrunkLevel(self.ItemAlcohol)
|
||||
end
|
||||
|
||||
-- Пустая тара
|
||||
if self.ItemEmpty then
|
||||
local inv = char:GetInventory()
|
||||
if inv then
|
||||
inv:Add(self.ItemEmpty)
|
||||
end
|
||||
end
|
||||
|
||||
-- Звук употребления из STALKER 2 Consumables
|
||||
if self.ItemType == "drink" then
|
||||
owner:EmitSound("Stalker2.Drink")
|
||||
elseif self.ItemType == "food" then
|
||||
owner:EmitSound("Stalker2.Eat")
|
||||
elseif self.ItemType == "medical" then
|
||||
owner:EmitSound("Stalker2.Healing")
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:PrimaryAttack()
|
||||
-- Ничего не делаем
|
||||
end
|
||||
|
||||
function SWEP:SecondaryAttack()
|
||||
-- Ничего не делаем
|
||||
end
|
||||
|
||||
function SWEP:Reload()
|
||||
-- Ничего не делаем
|
||||
end
|
||||
|
||||
function SWEP:Think()
|
||||
-- Можно добавить дополнительную логику
|
||||
end
|
||||
|
||||
function SWEP:Holster()
|
||||
-- Блокируем переключение во время употребления
|
||||
if self.Consuming then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function SWEP:OnRemove()
|
||||
-- Очистка
|
||||
end
|
||||
441
garrysmod/gamemodes/militaryrp/entities/weapons/ix_hands.lua
Normal file
441
garrysmod/gamemodes/militaryrp/entities/weapons/ix_hands.lua
Normal file
@@ -0,0 +1,441 @@
|
||||
|
||||
AddCSLuaFile()
|
||||
|
||||
if (CLIENT) then
|
||||
SWEP.PrintName = "Hands"
|
||||
SWEP.Slot = 0
|
||||
SWEP.SlotPos = 1
|
||||
SWEP.DrawAmmo = false
|
||||
SWEP.DrawCrosshair = true
|
||||
end
|
||||
|
||||
SWEP.Author = "Chessnut"
|
||||
SWEP.Instructions = [[Primary Fire: Throw/Punch
|
||||
Secondary Fire: Knock/Pickup
|
||||
Secondary Fire + Mouse: Rotate Object
|
||||
Reload: Drop]]
|
||||
SWEP.Purpose = "Hitting things and knocking on doors."
|
||||
SWEP.Drop = false
|
||||
|
||||
SWEP.ViewModelFOV = 45
|
||||
SWEP.ViewModelFlip = false
|
||||
SWEP.AnimPrefix = "rpg"
|
||||
|
||||
SWEP.ViewTranslation = 4
|
||||
if CLIENT then
|
||||
SWEP.NextAllowedPlayRateChange = 0
|
||||
end
|
||||
|
||||
SWEP.Primary.ClipSize = -1
|
||||
SWEP.Primary.DefaultClip = -1
|
||||
SWEP.Primary.Automatic = false
|
||||
SWEP.Primary.Ammo = ""
|
||||
SWEP.Primary.Damage = 5
|
||||
SWEP.Primary.Delay = 0.75
|
||||
|
||||
SWEP.Secondary.ClipSize = -1
|
||||
SWEP.Secondary.DefaultClip = 0
|
||||
SWEP.Secondary.Automatic = false
|
||||
SWEP.Secondary.Ammo = ""
|
||||
SWEP.Secondary.Delay = 0.5
|
||||
|
||||
SWEP.ViewModel = ""
|
||||
SWEP.WorldModel = ""
|
||||
|
||||
SWEP.UseHands = true
|
||||
SWEP.LowerAngles = Angle(0, 5, -14)
|
||||
SWEP.LowerAngles2 = Angle(0, 5, -19)
|
||||
SWEP.KnockViewPunchAngle = Angle(-1.3, 1.8, 0)
|
||||
|
||||
SWEP.FireWhenLowered = true
|
||||
SWEP.HoldType = "normal"
|
||||
|
||||
SWEP.holdDistance = 64
|
||||
SWEP.maxHoldDistance = 96 -- how far away the held object is allowed to travel before forcefully dropping
|
||||
SWEP.maxHoldStress = 4000 -- how much stress the held object can undergo before forcefully dropping
|
||||
|
||||
-- luacheck: globals ACT_VM_FISTS_DRAW ACT_VM_FISTS_HOLSTER
|
||||
ACT_VM_FISTS_DRAW = 2
|
||||
ACT_VM_FISTS_HOLSTER = 1
|
||||
|
||||
function SWEP:Initialize()
|
||||
self:SetHoldType(self.HoldType)
|
||||
|
||||
self.lastHand = 0
|
||||
self.maxHoldDistanceSquared = self.maxHoldDistance ^ 2
|
||||
self.heldObjectAngle = Angle(angle_zero)
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
function SWEP:DoDrawCrosshair(x, y)
|
||||
surface.SetDrawColor(255, 255, 255, 66)
|
||||
surface.DrawRect(x - 2, y - 2, 4, 4)
|
||||
end
|
||||
|
||||
hook.Add("CreateMove", "ixHandsCreateMove", function(cmd)
|
||||
if (LocalPlayer():GetLocalVar("bIsHoldingObject", false) and cmd:KeyDown(IN_ATTACK2)) then
|
||||
cmd:ClearMovement()
|
||||
local angle = RenderAngles()
|
||||
angle.z = 0
|
||||
cmd:SetViewAngles(angle)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function SWEP:Deploy()
|
||||
if (!IsValid(self:GetOwner())) then
|
||||
return
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
local viewModel = self:GetOwner():GetViewModel()
|
||||
if IsValid(viewModel) then
|
||||
viewModel:SetNoDraw(true)
|
||||
end
|
||||
end
|
||||
|
||||
self:DropObject()
|
||||
return true
|
||||
end
|
||||
|
||||
function SWEP:Precache()
|
||||
util.PrecacheSound("npc/vort/claw_swing1.wav")
|
||||
util.PrecacheSound("npc/vort/claw_swing2.wav")
|
||||
util.PrecacheSound("physics/plastic/plastic_box_impact_hard1.wav")
|
||||
util.PrecacheSound("physics/plastic/plastic_box_impact_hard2.wav")
|
||||
util.PrecacheSound("physics/plastic/plastic_box_impact_hard3.wav")
|
||||
util.PrecacheSound("physics/plastic/plastic_box_impact_hard4.wav")
|
||||
util.PrecacheSound("physics/wood/wood_crate_impact_hard2.wav")
|
||||
util.PrecacheSound("physics/wood/wood_crate_impact_hard3.wav")
|
||||
end
|
||||
|
||||
function SWEP:OnReloaded()
|
||||
self.maxHoldDistanceSquared = self.maxHoldDistance ^ 2
|
||||
self:DropObject()
|
||||
end
|
||||
|
||||
function SWEP:Holster()
|
||||
if (!IsValid(self:GetOwner())) then
|
||||
return
|
||||
end
|
||||
|
||||
local viewModel = self:GetOwner():GetViewModel()
|
||||
|
||||
if (IsValid(viewModel)) then
|
||||
viewModel:SetPlaybackRate(1)
|
||||
viewModel:ResetSequence(ACT_VM_FISTS_HOLSTER)
|
||||
if CLIENT then
|
||||
self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration()
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function SWEP:Think()
|
||||
if (!IsValid(self:GetOwner())) then
|
||||
return
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
local viewModel = self:GetOwner():GetViewModel()
|
||||
|
||||
if (IsValid(viewModel) and self.NextAllowedPlayRateChange < CurTime()) then
|
||||
viewModel:SetPlaybackRate(1)
|
||||
end
|
||||
else
|
||||
if (self:IsHoldingObject()) then
|
||||
local physics = self:GetHeldPhysicsObject()
|
||||
local bIsRagdoll = self.heldEntity:IsRagdoll()
|
||||
local holdDistance = bIsRagdoll and self.holdDistance * 0.5 or self.holdDistance
|
||||
local targetLocation = self:GetOwner():GetShootPos() + self:GetOwner():GetForward() * holdDistance
|
||||
|
||||
if (bIsRagdoll) then
|
||||
targetLocation.z = math.min(targetLocation.z, self:GetOwner():GetShootPos().z - 32)
|
||||
end
|
||||
|
||||
if (!IsValid(physics)) then
|
||||
self:DropObject()
|
||||
return
|
||||
end
|
||||
|
||||
if (physics:GetPos():DistToSqr(targetLocation) > self.maxHoldDistanceSquared) then
|
||||
self:DropObject()
|
||||
else
|
||||
local physicsObject = self.holdEntity:GetPhysicsObject()
|
||||
local currentPlayerAngles = self:GetOwner():EyeAngles()
|
||||
local client = self:GetOwner()
|
||||
|
||||
if (client:KeyDown(IN_ATTACK2)) then
|
||||
local cmd = client:GetCurrentCommand()
|
||||
self.heldObjectAngle:RotateAroundAxis(currentPlayerAngles:Forward(), cmd:GetMouseX() / 15)
|
||||
self.heldObjectAngle:RotateAroundAxis(currentPlayerAngles:Right(), cmd:GetMouseY() / 15)
|
||||
end
|
||||
|
||||
self.lastPlayerAngles = self.lastPlayerAngles or currentPlayerAngles
|
||||
self.heldObjectAngle.y = self.heldObjectAngle.y - math.AngleDifference(self.lastPlayerAngles.y, currentPlayerAngles.y)
|
||||
self.lastPlayerAngles = currentPlayerAngles
|
||||
|
||||
physicsObject:Wake()
|
||||
physicsObject:ComputeShadowControl({
|
||||
secondstoarrive = 0.01,
|
||||
pos = targetLocation,
|
||||
angle = self.heldObjectAngle,
|
||||
maxangular = 256,
|
||||
maxangulardamp = 10000,
|
||||
maxspeed = 256,
|
||||
maxspeeddamp = 10000,
|
||||
dampfactor = 0.8,
|
||||
teleportdistance = self.maxHoldDistance * 0.75,
|
||||
deltatime = FrameTime()
|
||||
})
|
||||
|
||||
if (physics:GetStress() > self.maxHoldStress) then
|
||||
self:DropObject()
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Prevents the camera from getting stuck when the object that the client is holding gets deleted.
|
||||
if(!IsValid(self.heldEntity) and self:GetOwner():GetLocalVar("bIsHoldingObject", true)) then
|
||||
self:GetOwner():SetLocalVar("bIsHoldingObject", false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:GetHeldPhysicsObject()
|
||||
return IsValid(self.heldEntity) and self.heldEntity:GetPhysicsObject() or nil
|
||||
end
|
||||
|
||||
function SWEP:CanHoldObject(entity)
|
||||
local physics = entity:GetPhysicsObject()
|
||||
|
||||
return IsValid(physics) and
|
||||
(physics:GetMass() <= ix.config.Get("maxHoldWeight", 100) and physics:IsMoveable()) and
|
||||
!self:IsHoldingObject() and
|
||||
!IsValid(entity.ixHeldOwner) and
|
||||
hook.Run("CanPlayerHoldObject", self:GetOwner(), entity)
|
||||
end
|
||||
|
||||
function SWEP:IsHoldingObject()
|
||||
return IsValid(self.heldEntity) and
|
||||
IsValid(self.heldEntity.ixHeldOwner) and
|
||||
self.heldEntity.ixHeldOwner == self:GetOwner()
|
||||
end
|
||||
|
||||
function SWEP:PickupObject(entity)
|
||||
if (self:IsHoldingObject() or
|
||||
!IsValid(entity) or
|
||||
!IsValid(entity:GetPhysicsObject())) then
|
||||
return
|
||||
end
|
||||
|
||||
local physics = entity:GetPhysicsObject()
|
||||
physics:EnableGravity(false)
|
||||
physics:AddGameFlag(FVPHYSICS_PLAYER_HELD)
|
||||
|
||||
entity.ixHeldOwner = self:GetOwner()
|
||||
entity.ixCollisionGroup = entity:GetCollisionGroup()
|
||||
entity:StartMotionController()
|
||||
entity:SetCollisionGroup(COLLISION_GROUP_WEAPON)
|
||||
|
||||
self.heldObjectAngle = entity:GetAngles()
|
||||
self.heldEntity = entity
|
||||
|
||||
self.holdEntity = ents.Create("prop_physics")
|
||||
self.holdEntity:SetPos(self.heldEntity:LocalToWorld(self.heldEntity:OBBCenter()))
|
||||
self.holdEntity:SetAngles(self.heldEntity:GetAngles())
|
||||
self.holdEntity:SetModel("models/weapons/w_bugbait.mdl")
|
||||
self.holdEntity:SetOwner(self:GetOwner())
|
||||
|
||||
self.holdEntity:SetNoDraw(true)
|
||||
self.holdEntity:SetNotSolid(true)
|
||||
self.holdEntity:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
|
||||
self.holdEntity:DrawShadow(false)
|
||||
|
||||
self.holdEntity:Spawn()
|
||||
|
||||
local trace = self:GetOwner():GetEyeTrace()
|
||||
local physicsObject = self.holdEntity:GetPhysicsObject()
|
||||
|
||||
if (IsValid(physicsObject)) then
|
||||
physicsObject:SetMass(2048)
|
||||
physicsObject:SetDamping(0, 1000)
|
||||
physicsObject:EnableGravity(false)
|
||||
physicsObject:EnableCollisions(false)
|
||||
physicsObject:EnableMotion(false)
|
||||
end
|
||||
|
||||
if (trace.Entity:IsRagdoll()) then
|
||||
local tracedEnt = trace.Entity
|
||||
self.holdEntity:SetPos(tracedEnt:GetBonePosition(tracedEnt:TranslatePhysBoneToBone(trace.PhysicsBone)))
|
||||
end
|
||||
|
||||
self.constraint = constraint.Weld(self.holdEntity, self.heldEntity, 0,
|
||||
trace.Entity:IsRagdoll() and trace.PhysicsBone or 0, 0, true, true)
|
||||
end
|
||||
|
||||
function SWEP:DropObject(bThrow)
|
||||
if (!IsValid(self.heldEntity) or self.heldEntity.ixHeldOwner != self:GetOwner()) then
|
||||
return
|
||||
end
|
||||
|
||||
self.lastPlayerAngles = nil
|
||||
self:GetOwner():SetLocalVar("bIsHoldingObject", false)
|
||||
|
||||
self.constraint:Remove()
|
||||
self.holdEntity:Remove()
|
||||
|
||||
self.heldEntity:StopMotionController()
|
||||
self.heldEntity:SetCollisionGroup(self.heldEntity.ixCollisionGroup or COLLISION_GROUP_NONE)
|
||||
|
||||
local physics = self:GetHeldPhysicsObject()
|
||||
physics:EnableGravity(true)
|
||||
physics:Wake()
|
||||
physics:ClearGameFlag(FVPHYSICS_PLAYER_HELD)
|
||||
|
||||
if (bThrow) then
|
||||
timer.Simple(0, function()
|
||||
if (IsValid(physics) and IsValid(self:GetOwner())) then
|
||||
physics:AddGameFlag(FVPHYSICS_WAS_THROWN)
|
||||
physics:ApplyForceCenter(self:GetOwner():GetAimVector() * ix.config.Get("throwForce", 732))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
self.heldEntity.ixHeldOwner = nil
|
||||
self.heldEntity.ixCollisionGroup = nil
|
||||
self.heldEntity = nil
|
||||
end
|
||||
|
||||
function SWEP:PlayPickupSound(surfaceProperty)
|
||||
local result = "Flesh.ImpactSoft"
|
||||
|
||||
if (surfaceProperty != nil) then
|
||||
local surfaceName = util.GetSurfacePropName(surfaceProperty)
|
||||
local soundName = surfaceName:gsub("^metal$", "SolidMetal") .. ".ImpactSoft"
|
||||
|
||||
if (sound.GetProperties(soundName)) then
|
||||
result = soundName
|
||||
end
|
||||
end
|
||||
|
||||
self:GetOwner():EmitSound(result, 75, 100, 40)
|
||||
end
|
||||
|
||||
function SWEP:Holster()
|
||||
if (!IsFirstTimePredicted() or CLIENT) then
|
||||
return
|
||||
end
|
||||
|
||||
self:DropObject()
|
||||
return true
|
||||
end
|
||||
|
||||
function SWEP:OnRemove()
|
||||
if (SERVER) then
|
||||
self:DropObject()
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:OwnerChanged()
|
||||
if (SERVER) then
|
||||
self:DropObject()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function SWEP:DoPunchAnimation()
|
||||
self.lastHand = math.abs(1 - self.lastHand)
|
||||
local sequence = 3 + self.lastHand -- punch anims
|
||||
local viewModel = self:GetOwner():GetViewModel()
|
||||
if (IsValid(viewModel)) then
|
||||
viewModel:SetPlaybackRate(0.5)
|
||||
viewModel:SetSequence(sequence)
|
||||
if CLIENT then
|
||||
self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() * 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function SWEP:PrimaryAttack()
|
||||
if (!IsFirstTimePredicted()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (SERVER and self:IsHoldingObject()) then
|
||||
self:DropObject(true)
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:SecondaryAttack()
|
||||
if (!IsFirstTimePredicted()) then
|
||||
return
|
||||
end
|
||||
|
||||
local data = {}
|
||||
data.start = self:GetOwner():GetShootPos()
|
||||
data.endpos = data.start + self:GetOwner():GetAimVector() * 84
|
||||
data.filter = {self, self:GetOwner()}
|
||||
local trace = util.TraceLine(data)
|
||||
local entity = trace.Entity
|
||||
|
||||
if CLIENT then
|
||||
local viewModel = self:GetOwner():GetViewModel()
|
||||
|
||||
if (IsValid(viewModel)) then
|
||||
viewModel:SetPlaybackRate(0.5)
|
||||
if CLIENT then
|
||||
self.NextAllowedPlayRateChange = CurTime() + viewModel:SequenceDuration() * 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
if (self:IsHoldingObject()) then
|
||||
self:DropObject()
|
||||
return
|
||||
end
|
||||
|
||||
if (IsValid(entity)) then
|
||||
if (entity:IsDoor()) then
|
||||
if (hook.Run("CanPlayerKnock", self:GetOwner(), entity) == false) then
|
||||
return
|
||||
end
|
||||
|
||||
self:GetOwner():ViewPunch(self.KnockViewPunchAngle)
|
||||
self:GetOwner():EmitSound("physics/wood/wood_crate_impact_hard"..math.random(2, 3)..".wav")
|
||||
self:GetOwner():SetAnimation(PLAYER_ATTACK1)
|
||||
|
||||
self:DoPunchAnimation()
|
||||
self:SetNextSecondaryFire(CurTime() + 0.4)
|
||||
self:SetNextPrimaryFire(CurTime() + 1)
|
||||
elseif (entity:IsPlayer() and ix.config.Get("allowPush", true)) then
|
||||
local direction = self:GetOwner():GetAimVector() * (300 + (self:GetOwner():GetCharacter():GetAttribute("str", 0) * 3))
|
||||
direction.z = 0
|
||||
entity:SetVelocity(direction)
|
||||
|
||||
self:GetOwner():EmitSound("Weapon_Crossbow.BoltHitBody")
|
||||
self:SetNextSecondaryFire(CurTime() + 1.5)
|
||||
self:SetNextPrimaryFire(CurTime() + 1.5)
|
||||
elseif (!entity:IsNPC() and self:CanHoldObject(entity)) then
|
||||
self:GetOwner():SetLocalVar("bIsHoldingObject", true)
|
||||
self:PickupObject(entity)
|
||||
self:PlayPickupSound(trace.SurfaceProps)
|
||||
self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:Reload()
|
||||
if (!IsFirstTimePredicted()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (SERVER and IsValid(self.heldEntity)) then
|
||||
self:DropObject()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,514 @@
|
||||
SWEP.Base = "tacrp_base"
|
||||
SWEP.Spawnable = true
|
||||
|
||||
AddCSLuaFile()
|
||||
|
||||
// names and stuff
|
||||
SWEP.PrintName = "Infantry First Aid Kit"
|
||||
SWEP.AbbrevName = "IFAK"
|
||||
SWEP.Category = "[FT] Медицина"
|
||||
|
||||
SWEP.SubCatTier = "9Special"
|
||||
SWEP.SubCatType = "9Special"
|
||||
|
||||
SWEP.Description = "Индивидуальная аптечка первой помощи. Содержит бинты, гемостаты и порошки QuikClot для остановки кровотечений."
|
||||
SWEP.Description_Quote = "Поддержка жизни в полевых условиях."
|
||||
|
||||
SWEP.Trivia_Manufacturer = "Military Issue"
|
||||
SWEP.Trivia_Year = "2000s"
|
||||
|
||||
SWEP.Faction = TacRP.FACTION_NEUTRAL
|
||||
SWEP.Credits = "Original: Firearms Source 2\nPort: TacRP"
|
||||
|
||||
SWEP.ViewModel = "models/weapons/v_ifak.mdl"
|
||||
SWEP.WorldModel = "models/Items/HealthKit.mdl"
|
||||
|
||||
SWEP.Slot = 4
|
||||
|
||||
SWEP.NoRanger = true
|
||||
SWEP.NoStatBox = true
|
||||
|
||||
// handling
|
||||
|
||||
SWEP.MoveSpeedMult = 1
|
||||
SWEP.ShootingSpeedMult = 1
|
||||
SWEP.SightedSpeedMult = 1
|
||||
|
||||
SWEP.AimDownSightsTime = 0.25
|
||||
SWEP.SprintToFireTime = 0.25
|
||||
|
||||
SWEP.Sway = 0
|
||||
SWEP.FreeAimMaxAngle = 0
|
||||
|
||||
// hold types
|
||||
|
||||
SWEP.HoldType = "slam"
|
||||
SWEP.HoldTypeSprint = "normal"
|
||||
SWEP.HoldTypeBlindFire = false
|
||||
|
||||
SWEP.GestureShoot = ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL
|
||||
SWEP.GestureReload = ACT_HL2MP_GESTURE_RELOAD_PISTOL
|
||||
|
||||
SWEP.PassiveAng = Angle(0, 0, 0)
|
||||
SWEP.PassivePos = Vector(0, -2, -4)
|
||||
|
||||
SWEP.BlindFireAng = Angle(0, 0, 0)
|
||||
SWEP.BlindFirePos = Vector(0, 0, 0)
|
||||
|
||||
SWEP.SprintAng = Angle(0, 30, 0)
|
||||
SWEP.SprintPos = Vector(2, 0, -12)
|
||||
|
||||
SWEP.SightAng = Angle(-0.5, 0, 0)
|
||||
SWEP.SightPos = Vector(-3.412, -6.4, -2.238)
|
||||
|
||||
SWEP.CorrectivePos = Vector(0, 0, 0)
|
||||
SWEP.CorrectiveAng = Angle(0, 0, 0)
|
||||
|
||||
SWEP.HolsterVisible = true
|
||||
SWEP.HolsterSlot = TacRP.HOLSTER_SLOT_GEAR
|
||||
SWEP.HolsterPos = Vector(2, 0, 0)
|
||||
SWEP.HolsterAng = Angle(-90, 0, 0)
|
||||
|
||||
// reload
|
||||
|
||||
SWEP.ClipSize = -1
|
||||
SWEP.Ammo = "none"
|
||||
|
||||
// sounds
|
||||
|
||||
SWEP.Sounds = {}
|
||||
SWEP.Sounds["bandage"] = {
|
||||
{time = 0.4, sound = "FAS2_Bandage.Retrieve"},
|
||||
{time = 1.25, sound = "FAS2_Bandage.Open"},
|
||||
{time = 2.15, sound = "FAS2_Hemostat.Retrieve"}
|
||||
}
|
||||
|
||||
SWEP.Sounds["quikclot"] = {
|
||||
{time = 0.3, sound = "FAS2_QuikClot.Retrieve"},
|
||||
{time = 1.45, sound = "FAS2_QuikClot.Loosen"},
|
||||
{time = 2.55, sound = "FAS2_QuikClot.Open"}
|
||||
}
|
||||
|
||||
SWEP.Sounds["suture"] = {
|
||||
{time = 0.3, sound = "FAS2_Hemostat.Retrieve"},
|
||||
{time = 3.5, sound = "FAS2_Hemostat.Close"}
|
||||
}
|
||||
|
||||
// attachments
|
||||
|
||||
SWEP.Attachments = {
|
||||
[1] = {
|
||||
PrintName = "Аксессуар",
|
||||
Category = "acc",
|
||||
AttachSound = "TacRP/weapons/flashlight_on.wav",
|
||||
DetachSound = "TacRP/weapons/flashlight_off.wav",
|
||||
},
|
||||
}
|
||||
|
||||
SWEP.FreeAim = false
|
||||
SWEP.Scope = false
|
||||
|
||||
SWEP.DrawCrosshair = true
|
||||
SWEP.DrawCrosshairInSprint = true
|
||||
|
||||
// Custom vars
|
||||
SWEP.EasterWait = 0
|
||||
|
||||
function SWEP:Initialize()
|
||||
self:SetHoldType(self.HoldType)
|
||||
|
||||
if CLIENT then
|
||||
self.SoundTime = 0
|
||||
self.SoundEntry = 1
|
||||
self.CurrentSoundTable = nil
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:Deploy()
|
||||
local owner = self:GetOwner()
|
||||
if SERVER and IsValid(owner) then
|
||||
owner:GiveAmmo(4, "Bandages", true)
|
||||
owner:GiveAmmo(3, "Quikclots", true)
|
||||
owner:GiveAmmo(2, "Hemostats", true)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function SWEP:Holster()
|
||||
if self.HealTime and CurTime() < self.HealTime then
|
||||
return false
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
self.CurrentSoundTable = nil
|
||||
self.SoundEntry = 1
|
||||
self.SoundTime = 0
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
local Mins, Maxs = Vector(-8, -8, -8), Vector(8, 8, 8)
|
||||
|
||||
function SWEP:FindHealTarget()
|
||||
local owner = self:GetOwner()
|
||||
if not IsValid(owner) then return owner end
|
||||
|
||||
local tr = util.TraceHull({
|
||||
start = owner:GetShootPos(),
|
||||
endpos = owner:GetShootPos() + owner:GetAimVector() * 50,
|
||||
filter = owner,
|
||||
mins = Mins,
|
||||
maxs = Maxs
|
||||
})
|
||||
|
||||
if tr.Hit and IsValid(tr.Entity) and tr.Entity:IsPlayer() then
|
||||
return tr.Entity
|
||||
end
|
||||
|
||||
return owner
|
||||
end
|
||||
|
||||
function SWEP:PlaySounds(soundTable)
|
||||
if not self.Sounds[soundTable] then return end
|
||||
|
||||
self.CurrentSoundTable = soundTable
|
||||
self.SoundEntry = 1
|
||||
self.SoundTime = CurTime()
|
||||
end
|
||||
|
||||
function SWEP:Think()
|
||||
local CT = CurTime()
|
||||
|
||||
-- Sound system
|
||||
if CLIENT and self.CurrentSoundTable then
|
||||
local sounds = self.Sounds[self.CurrentSoundTable]
|
||||
if sounds and sounds[self.SoundEntry] then
|
||||
local snd = sounds[self.SoundEntry]
|
||||
if CT >= self.SoundTime + snd.time then
|
||||
self:EmitSound(snd.sound, 70, 100)
|
||||
|
||||
if sounds[self.SoundEntry + 1] then
|
||||
self.SoundEntry = self.SoundEntry + 1
|
||||
else
|
||||
self.CurrentSoundTable = nil
|
||||
self.SoundEntry = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Healing process
|
||||
if self.CurrentHeal and CT >= self.HealTime then
|
||||
self:EndHealingProcess()
|
||||
end
|
||||
|
||||
-- Update viewmodel bodygroups
|
||||
if CLIENT and not self.CurrentHeal then
|
||||
self:UpdateBodygroups()
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:UpdateBodygroups()
|
||||
local owner = self:GetOwner()
|
||||
if not IsValid(owner) then return end
|
||||
|
||||
local vm = owner:GetViewModel()
|
||||
if not IsValid(vm) then return end
|
||||
|
||||
-- Bodygroup 2 - бинты (0-2)
|
||||
vm:SetBodygroup(2, math.Clamp(owner:GetAmmoCount("Bandages"), 0, 2))
|
||||
|
||||
-- Bodygroup 3 - гемостаты/квикклоты
|
||||
local hemostats = owner:GetAmmoCount("Hemostats")
|
||||
|
||||
if hemostats > 0 then
|
||||
vm:SetBodygroup(3, hemostats == 1 and 2 or 3)
|
||||
else
|
||||
local quikclots = owner:GetAmmoCount("Quikclots")
|
||||
vm:SetBodygroup(3, quikclots > 0 and 1 or 0)
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:EndHealingProcess()
|
||||
local owner = self:GetOwner()
|
||||
if not IsValid(owner) then return end
|
||||
|
||||
-- Play end animation
|
||||
local vm = owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
local anim = "idle"
|
||||
if self.CurrentHeal == "suture" and owner:GetAmmoCount("Hemostats") == 1 then
|
||||
anim = "bandage_end"
|
||||
else
|
||||
anim = self.CurrentHeal .. "_end"
|
||||
end
|
||||
|
||||
local seq = vm:LookupSequence(anim)
|
||||
if seq > 0 then
|
||||
vm:SendViewModelMatchingSequence(seq)
|
||||
end
|
||||
end
|
||||
|
||||
owner:RemoveAmmo(1, self.AmmoType)
|
||||
|
||||
if CLIENT then
|
||||
self:UpdateBodygroups()
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
if self.OwnHeal then
|
||||
owner:SetHealth(math.Clamp(owner:Health() + self.HealAmount, 0, owner:GetMaxHealth()))
|
||||
else
|
||||
if IsValid(self.Target) then
|
||||
self.Target:SetHealth(math.Clamp(self.Target:Health() + self.HealAmount, 0, self.Target:GetMaxHealth()))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.CurrentHeal = nil
|
||||
self.HealTime = nil
|
||||
self.HealAmount = nil
|
||||
self.Target = nil
|
||||
self.OwnHeal = false
|
||||
end
|
||||
|
||||
function SWEP:PrimaryAttack()
|
||||
if not IsFirstTimePredicted() then return end
|
||||
|
||||
local owner = self:GetOwner()
|
||||
if not IsValid(owner) then return end
|
||||
|
||||
local bandages = owner:GetAmmoCount("Bandages")
|
||||
if bandages <= 0 then return end
|
||||
|
||||
local target = self:FindHealTarget()
|
||||
if not IsValid(target) then return end
|
||||
|
||||
-- Проверка здоровья
|
||||
if CLIENT then
|
||||
if target:Health() >= 100 then return end
|
||||
else
|
||||
if target:Health() >= target:GetMaxHealth() then return end
|
||||
end
|
||||
|
||||
local CT = CurTime()
|
||||
|
||||
self:SetNextPrimaryFire(CT + 2.95)
|
||||
self:SetNextSecondaryFire(CT + 2.95)
|
||||
self.HealTime = CT + 2.5
|
||||
self.CurrentHeal = "bandage"
|
||||
self.AmmoType = "Bandages"
|
||||
self.HealAmount = 10
|
||||
self.Target = target
|
||||
self.OwnHeal = (target == owner)
|
||||
|
||||
-- Анимация
|
||||
local vm = owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
local seq = vm:LookupSequence("bandage")
|
||||
if seq > 0 then
|
||||
vm:SendViewModelMatchingSequence(seq)
|
||||
end
|
||||
end
|
||||
|
||||
-- Звуки
|
||||
self:PlaySounds("bandage")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function SWEP:SecondaryAttack()
|
||||
if not IsFirstTimePredicted() then return end
|
||||
|
||||
local owner = self:GetOwner()
|
||||
if not IsValid(owner) then return end
|
||||
|
||||
local hemostats = owner:GetAmmoCount("Hemostats")
|
||||
local quikclots = owner:GetAmmoCount("Quikclots")
|
||||
|
||||
if hemostats <= 0 and quikclots <= 0 then return end
|
||||
|
||||
local target = self:FindHealTarget()
|
||||
if not IsValid(target) then return end
|
||||
|
||||
-- Проверка здоровья
|
||||
if CLIENT then
|
||||
if target:Health() >= 100 then return end
|
||||
else
|
||||
if target:Health() >= target:GetMaxHealth() then return end
|
||||
end
|
||||
|
||||
local CT = CurTime()
|
||||
|
||||
if hemostats > 0 then
|
||||
-- Гемостат
|
||||
self:SetNextPrimaryFire(CT + 5.5)
|
||||
self:SetNextSecondaryFire(CT + 5.5)
|
||||
self.HealTime = CT + 4.5
|
||||
self.CurrentHeal = "suture"
|
||||
self.AmmoType = "Hemostats"
|
||||
self.HealAmount = 30
|
||||
|
||||
local vm = owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
local seq = vm:LookupSequence("suture")
|
||||
if seq > 0 then
|
||||
vm:SendViewModelMatchingSequence(seq)
|
||||
end
|
||||
end
|
||||
|
||||
self:PlaySounds("suture")
|
||||
else
|
||||
-- QuikClot
|
||||
self:SetNextPrimaryFire(CT + 4.65)
|
||||
self:SetNextSecondaryFire(CT + 4.65)
|
||||
self.HealTime = CT + 4.2
|
||||
self.CurrentHeal = "quikclot"
|
||||
self.AmmoType = "Quikclots"
|
||||
self.HealAmount = 20
|
||||
|
||||
local vm = owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
local seq = vm:LookupSequence("quikclot")
|
||||
if seq > 0 then
|
||||
vm:SendViewModelMatchingSequence(seq)
|
||||
end
|
||||
end
|
||||
|
||||
self:PlaySounds("quikclot")
|
||||
end
|
||||
|
||||
self.Target = target
|
||||
self.OwnHeal = (target == owner)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
local White = Color(255, 255, 255, 255)
|
||||
local Black = Color(0, 0, 0, 255)
|
||||
local Green = Color(202, 255, 163, 255)
|
||||
local Grey = Color(200, 200, 200, 255)
|
||||
|
||||
surface.CreateFont("IFAK_HUD24", {
|
||||
font = "Arial",
|
||||
size = 24,
|
||||
weight = 700,
|
||||
antialias = true
|
||||
})
|
||||
|
||||
surface.CreateFont("IFAK_HUD36", {
|
||||
font = "Arial",
|
||||
size = 36,
|
||||
weight = 700,
|
||||
antialias = true
|
||||
})
|
||||
|
||||
function SWEP:DrawHUD()
|
||||
local owner = self:GetOwner()
|
||||
if not IsValid(owner) then return end
|
||||
|
||||
local x, y = ScrW() / 2, ScrH() / 2
|
||||
|
||||
local target = self:FindHealTarget()
|
||||
local targetName = "СЕБЯ"
|
||||
if IsValid(target) and target != owner then
|
||||
targetName = target:Nick()
|
||||
end
|
||||
|
||||
draw.SimpleTextOutlined(
|
||||
"ЦЕЛЬ ЛЕЧЕНИЯ: " .. targetName,
|
||||
"IFAK_HUD24",
|
||||
x, y + 200,
|
||||
White, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER,
|
||||
2, Black
|
||||
)
|
||||
|
||||
-- Информация о расходниках
|
||||
local bandages = owner:GetAmmoCount("Bandages")
|
||||
local hemostats = owner:GetAmmoCount("Hemostats")
|
||||
local quikclots = owner:GetAmmoCount("Quikclots")
|
||||
|
||||
-- Левая сторона - Бинты
|
||||
draw.SimpleTextOutlined(
|
||||
"БИНТЫ: " .. bandages,
|
||||
"IFAK_HUD36",
|
||||
x - 100, y + 125,
|
||||
White, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
|
||||
if bandages > 0 then
|
||||
draw.SimpleTextOutlined(
|
||||
"ЛКМ - БИНТ",
|
||||
"IFAK_HUD24",
|
||||
x - 100, y + 100,
|
||||
Green, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
|
||||
draw.SimpleTextOutlined(
|
||||
"+10 HP",
|
||||
"IFAK_HUD24",
|
||||
x - 100, y + 75,
|
||||
Green, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
end
|
||||
|
||||
-- Правая сторона - Гемостаты/QuikClot
|
||||
if hemostats > 0 then
|
||||
draw.SimpleTextOutlined(
|
||||
"ГЕМОСТАТЫ: " .. hemostats,
|
||||
"IFAK_HUD36",
|
||||
x + 100, y + 125,
|
||||
White, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
|
||||
draw.SimpleTextOutlined(
|
||||
"ПКМ - ГЕМОСТАТ",
|
||||
"IFAK_HUD24",
|
||||
x + 100, y + 100,
|
||||
Green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
|
||||
draw.SimpleTextOutlined(
|
||||
"+30 HP",
|
||||
"IFAK_HUD24",
|
||||
x + 100, y + 75,
|
||||
Green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
else
|
||||
draw.SimpleTextOutlined(
|
||||
"QUIKCLOTS: " .. quikclots,
|
||||
"IFAK_HUD36",
|
||||
x + 100, y + 125,
|
||||
White, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
|
||||
if quikclots > 0 then
|
||||
draw.SimpleTextOutlined(
|
||||
"ПКМ - QUIKCLOT",
|
||||
"IFAK_HUD24",
|
||||
x + 100, y + 100,
|
||||
Green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
|
||||
draw.SimpleTextOutlined(
|
||||
"+20 HP",
|
||||
"IFAK_HUD24",
|
||||
x + 100, y + 75,
|
||||
Green, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP,
|
||||
2, Black
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,615 @@
|
||||
include('shared.lua')
|
||||
|
||||
if not cigaParticleEmitter then cigaParticleEmitter = ParticleEmitter(Vector(0,0,0)) end
|
||||
|
||||
function SWEP:DrawWorldModel()
|
||||
local ply = self:GetOwner()
|
||||
|
||||
local cigaScale = self.cigaScale or 1
|
||||
self:SetModelScale(cigaScale, 0)
|
||||
self:SetSubMaterial()
|
||||
|
||||
if IsValid(ply) then
|
||||
local modelStr = ply:GetModel():sub(1,17)
|
||||
local isPony = modelStr=="models/ppm/player" or modelStr=="models/mlp/player" or modelStr=="models/cppm/playe"
|
||||
|
||||
local bn = isPony and "LrigScull" or "ValveBiped.Bip01_R_Hand"
|
||||
if ply.cigaArmFullyUp then bn ="ValveBiped.Bip01_Head1" end
|
||||
local bon = ply:LookupBone(bn) or 0
|
||||
|
||||
local opos = self:GetPos()
|
||||
local oang = self:GetAngles()
|
||||
local bp,ba = ply:GetBonePosition(bon)
|
||||
if bp then opos = bp end
|
||||
if ba then oang = ba end
|
||||
|
||||
if ply.cigaArmFullyUp then
|
||||
--head position
|
||||
opos = opos + (oang:Forward()*0.95) + (oang:Right()*7) + (oang:Up()*0.035)
|
||||
oang:RotateAroundAxis(oang:Forward(),-100)
|
||||
oang:RotateAroundAxis(oang:Up(),100)
|
||||
opos = opos + (oang:Up()*(cigaScale-1)*-10.25)
|
||||
else
|
||||
--hand position
|
||||
oang:RotateAroundAxis(oang:Forward(),50)
|
||||
oang:RotateAroundAxis(oang:Right(),90)
|
||||
opos = opos + (oang:Forward()*2) + (oang:Up()*-4.5) + (oang:Right()*-2)
|
||||
oang:RotateAroundAxis(oang:Forward(),90)
|
||||
oang:RotateAroundAxis(oang:Up(),10)
|
||||
opos = opos + (oang:Up()*(cigaScale-1)*-10.25)
|
||||
opos = opos + (oang:Up() * 2)
|
||||
opos = opos + (oang:Right() * 0.5)
|
||||
opos = opos + (oang:Forward() * -1.5)
|
||||
end
|
||||
self:SetupBones()
|
||||
|
||||
local mrt = self:GetBoneMatrix(0)
|
||||
if mrt then
|
||||
mrt:SetTranslation(opos)
|
||||
mrt:SetAngles(oang)
|
||||
|
||||
self:SetBoneMatrix(0, mrt)
|
||||
end
|
||||
end
|
||||
|
||||
self:DrawModel()
|
||||
end
|
||||
|
||||
function SWEP:GetViewModelPosition(pos, ang)
|
||||
--mouth pos
|
||||
local vmpos1=self.cigaVMPos1 or Vector(18.5,-3.4,-3.25)
|
||||
local vmang1=self.cigaVMAng1 or Vector(170,-180,20)
|
||||
--hand pos
|
||||
local vmpos2=self.cigaVMPos2 or Vector(24,-8,-11.2)
|
||||
local vmang2=self.cigaVMAng2 or Vector(120,-180,150)
|
||||
|
||||
if not LocalPlayer().cigaArmTime then LocalPlayer().cigaArmTime=0 end
|
||||
local lerp = math.Clamp((os.clock()-LocalPlayer().cigaArmTime)*3,0,1)
|
||||
if LocalPlayer().cigaArm then lerp = 1-lerp end
|
||||
/*
|
||||
local newpos = LerpVector(lerp,vmpos1,vmpos2)
|
||||
local newang = LerpVector(lerp,vmang1,vmang2)
|
||||
--I have a good reason for doing it like this
|
||||
newang = Angle(newang.x,newang.y,newang.z)
|
||||
|
||||
pos,ang = LocalToWorld(newpos,newang,pos,ang)*/
|
||||
local difvec = Vector(-10,-3.5,-12)--vmpos1 - vmpos2
|
||||
local orig = Vector(0,0,0)
|
||||
local topos = orig+difvec
|
||||
|
||||
local difang = Vector(-30,0,0)--vmang1 - vmang2
|
||||
local origang = Vector(0,0,0)
|
||||
local toang = origang+difang
|
||||
|
||||
|
||||
|
||||
local newpos = LerpVector(lerp,topos,orig)
|
||||
local newang = LerpVector(lerp,toang,origang)
|
||||
|
||||
newang = Angle(newang.x, newang.y, newang.z)
|
||||
|
||||
|
||||
pos,ang = LocalToWorld(newpos,newang,pos,ang)
|
||||
return pos, ang
|
||||
end
|
||||
|
||||
sound.Add({
|
||||
name = "ciga_inhale",
|
||||
channel = CHAN_WEAPON,
|
||||
volume = 0.24,
|
||||
level = 60,
|
||||
pitch = { 95 },
|
||||
sound = "cigainhale.wav"
|
||||
})
|
||||
|
||||
net.Receive("ciga",function()
|
||||
local ply = net.ReadEntity()
|
||||
local amt = net.ReadInt(8)
|
||||
local fx = net.ReadInt(8)
|
||||
if !IsValid(ply) then return end
|
||||
|
||||
if amt>=50 then
|
||||
ply:EmitSound("cigacough1.wav",90)
|
||||
|
||||
for i=1,200 do
|
||||
local d=i+10
|
||||
if i>140 then d=d+150 end
|
||||
timer.Simple((d-1)*0.003,function() ciga_do_pulse(ply, 1, 100, fx) end)
|
||||
end
|
||||
|
||||
return
|
||||
elseif amt>=35 then
|
||||
ply:EmitSound("cigabreath2.wav",75,100,0.7)
|
||||
elseif amt>=10 then
|
||||
ply:EmitSound("cigabreath1.wav",70,130-math.min(100,amt*2),0.4+(amt*0.005))
|
||||
end
|
||||
|
||||
for i=1,amt*2 do
|
||||
timer.Simple((i-1)*0.02,function() ciga_do_pulse(ply,math.floor(((amt*2)-i)/10), fx==2 and 100 or 0, fx) end)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("cigaArm",function()
|
||||
local ply = net.ReadEntity()
|
||||
local z = net.ReadBool()
|
||||
if !IsValid(ply) then return end
|
||||
if ply.cigaArm != z then
|
||||
if z then
|
||||
timer.Simple(0.3, function()
|
||||
if !IsValid(ply) then return end
|
||||
if ply.cigaArm then ply:EmitSound("ciga_inhale") end
|
||||
end)
|
||||
else
|
||||
ply:StopSound("ciga_inhale")
|
||||
end
|
||||
ply.cigaArm = z
|
||||
ply.cigaArmTime = os.clock()
|
||||
local m = 0
|
||||
if z then m = 1 end
|
||||
|
||||
for i=0,9 do
|
||||
timer.Simple(i/30,function() ciga_interpolate_arm(ply,math.abs(m-((9-i)/10)),z and 0 or 0.2) end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("cigaTalking",function()
|
||||
local ply = net.ReadEntity()
|
||||
if IsValid(ply) then ply.cigaTalkingEndtime = net.ReadFloat() end
|
||||
end)
|
||||
|
||||
function ciga_interpolate_arm(ply, mult, mouth_delay)
|
||||
if !IsValid(ply) then return end
|
||||
|
||||
if mouth_delay>0 then
|
||||
timer.Simple(mouth_delay,function() if IsValid(ply) then ply.cigaMouthOpenAmt = mult end end)
|
||||
else
|
||||
ply.cigaMouthOpenAmt = mult
|
||||
end
|
||||
|
||||
local b1 = ply:LookupBone("ValveBiped.Bip01_R_Upperarm")
|
||||
local b2 = ply:LookupBone("ValveBiped.Bip01_R_Forearm")
|
||||
if (not b1) or (not b2) then return end
|
||||
ply:ManipulateBoneAngles(b1,Angle(20*mult,-62*mult,10*mult))
|
||||
ply:ManipulateBoneAngles(b2,Angle(-5*mult,-10*mult,0))
|
||||
if mult==1 then ply.cigaArmFullyUp=true else ply.cigaArmFullyUp=false end
|
||||
end
|
||||
|
||||
--this makes the mouth opening work without clobbering other addons
|
||||
hook.Add("InitPostEntity", "cigaMouthMoveSetup", function()
|
||||
timer.Simple(1, function()
|
||||
if ciga_OriginalMouthMove ~= nil then return end
|
||||
|
||||
ciga_OriginalMouthMove = GAMEMODE.MouthMoveAnimation
|
||||
|
||||
function GAMEMODE:MouthMoveAnimation(ply)
|
||||
--run the base MouthMoveAnimation if player isn't vaping/cigatalking
|
||||
if ((ply.cigaMouthOpenAmt or 0) == 0) and ((ply.cigaTalkingEndtime or 0) < CurTime()) then
|
||||
return ciga_OriginalMouthMove(GAMEMODE, ply)
|
||||
end
|
||||
|
||||
local FlexNum = ply:GetFlexNum() - 1
|
||||
if ( FlexNum <= 0 ) then return end
|
||||
for i = 0, FlexNum - 1 do
|
||||
local Name = ply:GetFlexName(i)
|
||||
if ( Name == "jaw_drop" || Name == "right_part" || Name == "left_part" || Name == "right_mouth_drop" || Name == "left_mouth_drop" ) then
|
||||
ply:SetFlexWeight(i, math.max(((ply.cigaMouthOpenAmt or 0)*0.5), math.Clamp(((ply.cigaTalkingEndtime or 0)-CurTime())*3.0, 0, 1)*math.Rand(0.1,0.8) ))
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
function ciga_do_pulse(ply, amt, spreadadd, fx)
|
||||
if !IsValid(ply) then return end
|
||||
|
||||
if ply:WaterLevel()==3 then return end
|
||||
|
||||
if not spreadadd then spreadadd=0 end
|
||||
|
||||
local attachid = ply:LookupAttachment("eyes")
|
||||
cigaParticleEmitter:SetPos(LocalPlayer():GetPos())
|
||||
|
||||
local angpos = ply:GetAttachment(attachid) or {Ang=Angle(0,0,0), Pos=Vector(0,0,0)}
|
||||
local fwd
|
||||
local pos
|
||||
|
||||
if (ply != LocalPlayer()) then
|
||||
fwd = (angpos.Ang:Forward()-angpos.Ang:Up()):GetNormalized()
|
||||
pos = angpos.Pos + (fwd*3.5)
|
||||
else
|
||||
fwd = ply:GetAimVector():GetNormalized()
|
||||
pos = ply:GetShootPos() + fwd*1.5 + gui.ScreenToVector( ScrW()/2, ScrH() )*5
|
||||
end
|
||||
|
||||
fwd = ply:GetAimVector():GetNormalized()
|
||||
|
||||
for i = 1,amt do
|
||||
if !IsValid(ply) then return end
|
||||
local particle = cigaParticleEmitter:Add(string.format("particle/smokesprites_00%02d",math.random(7,16)), pos )
|
||||
if particle then
|
||||
local dir = VectorRand():GetNormalized() * ((amt+5)/10)
|
||||
ciga_do_particle(particle, (ply:GetVelocity()*0.25)+(((fwd*9)+dir):GetNormalized() * math.Rand(50,80) * (amt + 1) * 0.2), fx)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ciga_do_particle(particle, vel, fx)
|
||||
particle:SetColor(255,255,255,255)
|
||||
if fx == 3 then particle:SetColor(100,100,100,100) end
|
||||
if fx >= 4 then
|
||||
local c = JuicycigaJuices[fx-3].color
|
||||
if c == nil then c = HSVToColor(math.random(0,359),1,1) end
|
||||
particle:SetColor(c.r, c.g, c.b, 255)
|
||||
end
|
||||
|
||||
local mega = 1
|
||||
if fx == 2 then mega = 4 end
|
||||
mega = mega * 0.3
|
||||
|
||||
particle:SetVelocity( vel * mega )
|
||||
particle:SetGravity( Vector(0,0,1.5) )
|
||||
particle:SetLifeTime(0)
|
||||
particle:SetDieTime(math.Rand(80,100)*0.11*mega)
|
||||
particle:SetStartSize(3*mega)
|
||||
particle:SetEndSize(40*mega*mega)
|
||||
particle:SetStartAlpha(150)
|
||||
particle:SetEndAlpha(0)
|
||||
particle:SetCollide(true)
|
||||
particle:SetBounce(0.25)
|
||||
particle:SetRoll(math.Rand(0,360))
|
||||
particle:SetRollDelta(0.01*math.Rand(-40,40))
|
||||
particle:SetAirResistance(50)
|
||||
end
|
||||
|
||||
matproxy.Add({
|
||||
name = "cigaTankColor",
|
||||
init = function( self, mat, values )
|
||||
self.ResultTo = values.resultvar
|
||||
end,
|
||||
bind = function( self, mat, ent )
|
||||
if ( !IsValid( ent ) ) then return end
|
||||
if ent:GetClass()=="viewmodel" then
|
||||
ent=ent:GetOwner()
|
||||
if ( !IsValid( ent ) or !ent:IsPlayer() ) then return end
|
||||
ent=ent:GetActiveWeapon()
|
||||
if ( !IsValid( ent ) ) then return end
|
||||
end
|
||||
local v = ent.cigaTankColor or Vector(0.3,0.3,0.3)
|
||||
if v==Vector(-1,-1,-1) then
|
||||
local c = HSVToColor((CurTime()*60)%360,0.9,0.9)
|
||||
v = Vector(c.r,c.g,c.b)/255.0
|
||||
end
|
||||
mat:SetVector(self.ResultTo, v)
|
||||
end
|
||||
})
|
||||
|
||||
matproxy.Add({
|
||||
name = "cigaAccentColor",
|
||||
init = function( self, mat, values )
|
||||
self.ResultTo = values.resultvar
|
||||
end,
|
||||
bind = function( self, mat, ent )
|
||||
if ( !IsValid( ent ) ) then return end
|
||||
local o = ent:GetOwner()
|
||||
if ent:GetClass()=="viewmodel" then
|
||||
if (!IsValid(o)) or (!o:IsPlayer()) then return end
|
||||
ent=o:GetActiveWeapon()
|
||||
if ( !IsValid( ent ) ) then return end
|
||||
end
|
||||
local special = false
|
||||
local col = ent.cigaAccentColor or special and Vector(1,0.8,0) or Vector(1,1,1)
|
||||
if col==Vector(-1,-1,-1) then
|
||||
col=Vector(1,1,1)
|
||||
if IsValid(o) then col=o:GetWeaponColor() end
|
||||
end
|
||||
mat:SetVector(self.ResultTo, col)
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
--Swep Construction Kit code--
|
||||
|
||||
if CLIENT then
|
||||
|
||||
SWEP.vRenderOrder = nil
|
||||
function SWEP:ViewModelDrawn()
|
||||
|
||||
local vm = self.Owner:GetViewModel()
|
||||
if !IsValid(vm) then return end
|
||||
|
||||
if (!self.VElements) then return end
|
||||
|
||||
self:UpdateBonePositions(vm)
|
||||
|
||||
if (!self.vRenderOrder) then
|
||||
|
||||
// we build a render order because sprites need to be drawn after models
|
||||
self.vRenderOrder = {}
|
||||
|
||||
for k, v in pairs( self.VElements ) do
|
||||
if (v.type == "Model") then
|
||||
table.insert(self.vRenderOrder, 1, k)
|
||||
elseif (v.type == "Sprite" or v.type == "Quad") then
|
||||
table.insert(self.vRenderOrder, k)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
for k, name in ipairs( self.vRenderOrder ) do
|
||||
|
||||
local v = self.VElements[name]
|
||||
if (!v) then self.vRenderOrder = nil break end
|
||||
if (v.hide) then continue end
|
||||
|
||||
local model = v.modelEnt
|
||||
local sprite = v.spriteMaterial
|
||||
|
||||
if (!v.bone) then continue end
|
||||
|
||||
local pos, ang = self:GetBoneOrientation( self.VElements, v, vm )
|
||||
|
||||
if (!pos) then continue end
|
||||
|
||||
if (v.type == "Model" and IsValid(model)) then
|
||||
|
||||
model:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z )
|
||||
ang:RotateAroundAxis(ang:Up(), v.angle.y)
|
||||
ang:RotateAroundAxis(ang:Right(), v.angle.p)
|
||||
ang:RotateAroundAxis(ang:Forward(), v.angle.r)
|
||||
|
||||
model:SetAngles(ang)
|
||||
//model:SetModelScale(v.size)
|
||||
local matrix = Matrix()
|
||||
matrix:Scale(v.size)
|
||||
model:EnableMatrix( "RenderMultiply", matrix )
|
||||
|
||||
if (v.material == "") then
|
||||
model:SetMaterial("")
|
||||
elseif (model:GetMaterial() != v.material) then
|
||||
model:SetMaterial( v.material )
|
||||
end
|
||||
|
||||
if (v.skin and v.skin != model:GetSkin()) then
|
||||
model:SetSkin(v.skin)
|
||||
end
|
||||
|
||||
if (v.bodygroup) then
|
||||
for k, v in pairs( v.bodygroup ) do
|
||||
if (model:GetBodygroup(k) != v) then
|
||||
model:SetBodygroup(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (v.surpresslightning) then
|
||||
render.SuppressEngineLighting(true)
|
||||
end
|
||||
|
||||
render.SetColorModulation(v.color.r/255, v.color.g/255, v.color.b/255)
|
||||
render.SetBlend(v.color.a/255)
|
||||
model:DrawModel()
|
||||
render.SetBlend(1)
|
||||
render.SetColorModulation(1, 1, 1)
|
||||
|
||||
if (v.surpresslightning) then
|
||||
render.SuppressEngineLighting(false)
|
||||
end
|
||||
|
||||
elseif (v.type == "Sprite" and sprite) then
|
||||
|
||||
local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z
|
||||
render.SetMaterial(sprite)
|
||||
render.DrawSprite(drawpos, v.size.x, v.size.y, v.color)
|
||||
|
||||
elseif (v.type == "Quad" and v.draw_func) then
|
||||
|
||||
local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z
|
||||
ang:RotateAroundAxis(ang:Up(), v.angle.y)
|
||||
ang:RotateAroundAxis(ang:Right(), v.angle.p)
|
||||
ang:RotateAroundAxis(ang:Forward(), v.angle.r)
|
||||
|
||||
cam.Start3D2D(drawpos, ang, v.size)
|
||||
v.draw_func( self )
|
||||
cam.End3D2D()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SWEP:GetBoneOrientation( basetab, tab, ent, bone_override )
|
||||
|
||||
local bone, pos, ang
|
||||
if (tab.rel and tab.rel != "") then
|
||||
|
||||
local v = basetab[tab.rel]
|
||||
|
||||
if (!v) then return end
|
||||
|
||||
// Technically, if there exists an element with the same name as a bone
|
||||
// you can get in an infinite loop. Let's just hope nobody's that stupid.
|
||||
pos, ang = self:GetBoneOrientation( basetab, v, ent )
|
||||
|
||||
if (!pos) then return end
|
||||
|
||||
pos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z
|
||||
ang:RotateAroundAxis(ang:Up(), v.angle.y)
|
||||
ang:RotateAroundAxis(ang:Right(), v.angle.p)
|
||||
ang:RotateAroundAxis(ang:Forward(), v.angle.r)
|
||||
|
||||
else
|
||||
|
||||
bone = ent:LookupBone(bone_override or tab.bone)
|
||||
|
||||
if (!bone) then return end
|
||||
|
||||
pos, ang = Vector(0,0,0), Angle(0,0,0)
|
||||
local m = ent:GetBoneMatrix(bone)
|
||||
if (m) then
|
||||
pos, ang = m:GetTranslation(), m:GetAngles()
|
||||
end
|
||||
|
||||
if (IsValid(self.Owner) and self.Owner:IsPlayer() and
|
||||
ent == self.Owner:GetViewModel() and self.ViewModelFlip) then
|
||||
ang.r = -ang.r // Fixes mirrored models
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return pos, ang
|
||||
end
|
||||
|
||||
function SWEP:CreateModels( tab )
|
||||
|
||||
if (!tab) then return end
|
||||
|
||||
// Create the clientside models here because Garry says we can't do it in the render hook
|
||||
for k, v in pairs( tab ) do
|
||||
if (v.type == "Model" and v.model and v.model != "" and (!IsValid(v.modelEnt) or v.createdModel != v.model) and
|
||||
string.find(v.model, ".mdl") and file.Exists (v.model, "GAME") ) then
|
||||
|
||||
v.modelEnt = ClientsideModel(v.model, RENDER_GROUP_VIEW_MODEL_OPAQUE)
|
||||
if (IsValid(v.modelEnt)) then
|
||||
v.modelEnt:SetPos(self:GetPos())
|
||||
v.modelEnt:SetAngles(self:GetAngles())
|
||||
v.modelEnt:SetParent(self)
|
||||
v.modelEnt:SetNoDraw(true)
|
||||
v.createdModel = v.model
|
||||
else
|
||||
v.modelEnt = nil
|
||||
end
|
||||
|
||||
elseif (v.type == "Sprite" and v.sprite and v.sprite != "" and (!v.spriteMaterial or v.createdSprite != v.sprite)
|
||||
and file.Exists ("materials/"..v.sprite..".vmt", "GAME")) then
|
||||
|
||||
local name = v.sprite.."-"
|
||||
local params = { ["$basetexture"] = v.sprite }
|
||||
// make sure we create a unique name based on the selected options
|
||||
local tocheck = { "nocull", "additive", "vertexalpha", "vertexcolor", "ignorez" }
|
||||
for i, j in pairs( tocheck ) do
|
||||
if (v[j]) then
|
||||
params["$"..j] = 1
|
||||
name = name.."1"
|
||||
else
|
||||
name = name.."0"
|
||||
end
|
||||
end
|
||||
|
||||
v.createdSprite = v.sprite
|
||||
v.spriteMaterial = CreateMaterial(name,"UnlitGeneric",params)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local allbones
|
||||
local hasGarryFixedBoneScalingYet = false
|
||||
|
||||
function SWEP:UpdateBonePositions(vm)
|
||||
|
||||
if self.ViewModelBoneMods then
|
||||
|
||||
if (!vm:GetBoneCount()) then return end
|
||||
|
||||
// !! WORKAROUND !! //
|
||||
// We need to check all model names :/
|
||||
local loopthrough = self.ViewModelBoneMods
|
||||
if (!hasGarryFixedBoneScalingYet) then
|
||||
allbones = {}
|
||||
for i=0, vm:GetBoneCount() do
|
||||
local bonename = vm:GetBoneName(i)
|
||||
if (self.ViewModelBoneMods[bonename]) then
|
||||
allbones[bonename] = self.ViewModelBoneMods[bonename]
|
||||
else
|
||||
allbones[bonename] = {
|
||||
scale = Vector(1,1,1),
|
||||
pos = Vector(0,0,0),
|
||||
angle = Angle(0,0,0)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
loopthrough = allbones
|
||||
end
|
||||
// !! ----------- !! //
|
||||
|
||||
for k, v in pairs( loopthrough ) do
|
||||
local bone = vm:LookupBone(k)
|
||||
if (!bone) then continue end
|
||||
|
||||
// !! WORKAROUND !! //
|
||||
local s = Vector(v.scale.x,v.scale.y,v.scale.z)
|
||||
local p = Vector(v.pos.x,v.pos.y,v.pos.z)
|
||||
local ms = Vector(1,1,1)
|
||||
if (!hasGarryFixedBoneScalingYet) then
|
||||
local cur = vm:GetBoneParent(bone)
|
||||
while(cur >= 0) do
|
||||
local pscale = loopthrough[vm:GetBoneName(cur)].scale
|
||||
ms = ms * pscale
|
||||
cur = vm:GetBoneParent(cur)
|
||||
end
|
||||
end
|
||||
|
||||
s = s * ms
|
||||
// !! ----------- !! //
|
||||
|
||||
if vm:GetManipulateBoneScale(bone) != s then
|
||||
vm:ManipulateBoneScale( bone, s )
|
||||
end
|
||||
if vm:GetManipulateBoneAngles(bone) != v.angle then
|
||||
vm:ManipulateBoneAngles( bone, v.angle )
|
||||
end
|
||||
if vm:GetManipulateBonePosition(bone) != p then
|
||||
vm:ManipulateBonePosition( bone, p )
|
||||
end
|
||||
end
|
||||
else
|
||||
self:ResetBonePositions(vm)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SWEP:ResetBonePositions(vm)
|
||||
|
||||
if (!vm:GetBoneCount()) then return end
|
||||
for i=0, vm:GetBoneCount() do
|
||||
vm:ManipulateBoneScale( i, Vector(1, 1, 1) )
|
||||
vm:ManipulateBoneAngles( i, Angle(0, 0, 0) )
|
||||
vm:ManipulateBonePosition( i, Vector(0, 0, 0) )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
/**************************
|
||||
Global utility code
|
||||
**************************/
|
||||
|
||||
// Fully copies the table, meaning all tables inside this table are copied too and so on (normal table.Copy copies only their reference).
|
||||
// Does not copy entities of course, only copies their reference.
|
||||
// WARNING: do not use on tables that contain themselves somewhere down the line or you'll get an infinite loop
|
||||
function table.FullCopy( tab )
|
||||
|
||||
if (!tab) then return nil end
|
||||
|
||||
local res = {}
|
||||
for k, v in pairs( tab ) do
|
||||
if (type(v) == "table") then
|
||||
res[k] = table.FullCopy(v) // recursion ho!
|
||||
elseif (type(v) == "Vector") then
|
||||
res[k] = Vector(v.x, v.y, v.z)
|
||||
elseif (type(v) == "Angle") then
|
||||
res[k] = Angle(v.p, v.y, v.r)
|
||||
else
|
||||
res[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return res
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
AddCSLuaFile ("cl_init.lua")
|
||||
AddCSLuaFile ("shared.lua")
|
||||
include ("shared.lua")
|
||||
|
||||
util.AddNetworkString("ciga")
|
||||
util.AddNetworkString("cigaArm")
|
||||
util.AddNetworkString("cigaTalking")
|
||||
|
||||
function cigaUpdate(ply, cigaID)
|
||||
if not ply.cigaCount then ply.cigaCount = 0 end
|
||||
if not ply.cantStartciga then ply.cantStartciga=false end
|
||||
if ply.cigaCount == 0 and ply.cantStartciga then return end
|
||||
|
||||
ply.cigaID = cigaID
|
||||
ply.cigaCount = ply.cigaCount + 1
|
||||
if ply.cigaCount == 1 then
|
||||
ply.cigaArm = true
|
||||
net.Start("cigaArm")
|
||||
net.WriteEntity(ply)
|
||||
net.WriteBool(true)
|
||||
net.Broadcast()
|
||||
end
|
||||
if ply.cigaCount >= 50 then
|
||||
ply.cantStartciga = true
|
||||
Releaseciga(ply)
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add("KeyRelease","DocigaHook",function(ply, key)
|
||||
if key == IN_ATTACK then
|
||||
Releaseciga(ply)
|
||||
ply.cantStartciga=false
|
||||
end
|
||||
end)
|
||||
|
||||
function Releaseciga(ply)
|
||||
if not ply.cigaCount then ply.cigaCount = 0 end
|
||||
if IsValid(ply:GetActiveWeapon()) and ply:GetActiveWeapon():GetClass():sub(1,11) == "weapon_ciga" then
|
||||
if ply.cigaCount >= 5 then
|
||||
net.Start("ciga")
|
||||
net.WriteEntity(ply)
|
||||
net.WriteInt(ply.cigaCount, 8)
|
||||
net.WriteInt(ply.cigaID + (ply:GetActiveWeapon().juiceID or 0), 8)
|
||||
net.Broadcast()
|
||||
end
|
||||
end
|
||||
if ply.cigaArm then
|
||||
ply.cigaArm = false
|
||||
net.Start("cigaArm")
|
||||
net.WriteEntity(ply)
|
||||
net.WriteBool(false)
|
||||
net.Broadcast()
|
||||
end
|
||||
ply.cigaCount=0
|
||||
end
|
||||
@@ -0,0 +1,131 @@
|
||||
SWEP.PrintName = "Marlboro"
|
||||
|
||||
SWEP.IconLetter = ""
|
||||
SWEP.Author = "AeroMatix"
|
||||
SWEP.Category = "[FT] Атмосфера" -- Support the author by not changing this.
|
||||
SWEP.Slot = 1
|
||||
SWEP.SlotPos = 0
|
||||
|
||||
SWEP.ViewModelFOV = 62 --default
|
||||
|
||||
SWEP.BounceWeaponIcon = false
|
||||
|
||||
SWEP.ViewModel = "models/oldcigshib.mdl"
|
||||
SWEP.WorldModel = "models/oldcigshib.mdl"
|
||||
SWEP.Spawnable = true
|
||||
SWEP.AdminOnly = false
|
||||
|
||||
SWEP.Primary.Clipsize = -1
|
||||
SWEP.Primary.DefaultClip = -1
|
||||
SWEP.Primary.Automatic = true
|
||||
SWEP.Primary.Ammo = "none"
|
||||
|
||||
SWEP.Secondary.Clipsize = -1
|
||||
SWEP.Secondary.DefaultClip = -1
|
||||
SWEP.Secondary.Automatic = false
|
||||
SWEP.Secondary.Ammo = "none"
|
||||
|
||||
SWEP.DrawAmmo = false
|
||||
SWEP.HoldType = "slam"
|
||||
|
||||
SWEP.cigaID = 1
|
||||
|
||||
function SWEP:Deploy()
|
||||
self:SetHoldType("slam")
|
||||
end
|
||||
|
||||
function SWEP:SecondaryAttack()
|
||||
end
|
||||
|
||||
function SWEP:Initialize()
|
||||
|
||||
if !self.CigaInitialized then
|
||||
self.CigaInitialized = true
|
||||
self.VElements = {
|
||||
["ciga"] = { type = "Model", model = self.ViewModel, bone = "ValveBiped.Bip01_Spine4", rel = "", pos = Vector(-7.1, -2.401, 23.377), angle = Angle(111.039, 10.519, 0), size = Vector(1, 1, 1), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} }
|
||||
}
|
||||
--self.VElements["ciga"].model = self.ViewModel
|
||||
self.OldCigaModel = self.ViewModel
|
||||
self.ViewModel = "models/weapons/c_slam.mdl"
|
||||
self.UseHands = true
|
||||
self.ViewModelFlip = true
|
||||
self.ShowViewModel = true
|
||||
self.ShowWorldModel = true
|
||||
self.ViewModelBoneMods = {
|
||||
["ValveBiped.Bip01_L_Finger1"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(-23.334, -12.223, -32.223) },
|
||||
["ValveBiped.Bip01_L_Finger12"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, -21.112, 0) },
|
||||
["ValveBiped.Bip01_L_Finger4"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, -65.556, 0) },
|
||||
["ValveBiped.Bip01_R_UpperArm"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, 72.222, -41.112) },
|
||||
["ValveBiped.Bip01_L_Finger0"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(10, 1.11, -1.111) },
|
||||
["Detonator"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) },
|
||||
["ValveBiped.Bip01_L_Hand"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(-27.778, 1.11, -7.778) },
|
||||
["Slam_panel"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) },
|
||||
["ValveBiped.Bip01_L_Finger2"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, -47.778, 0) },
|
||||
["ValveBiped.Bip01_L_Finger3"] = { scale = Vector(1, 1, 1), pos = Vector(0, 0, 0), angle = Angle(0, -43.334, 0) },
|
||||
["Slam_base"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) },
|
||||
["ValveBiped.Bip01_R_Hand"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) }
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
|
||||
// Create a new table for every weapon instance
|
||||
self.VElements = table.FullCopy( self.VElements )
|
||||
self.WElements = table.FullCopy( self.WElements )
|
||||
self.ViewModelBoneMods = table.FullCopy( self.ViewModelBoneMods )
|
||||
|
||||
self:CreateModels(self.VElements) // create viewmodels
|
||||
self:CreateModels(self.WElements) // create worldmodels
|
||||
|
||||
// init view model bone build function
|
||||
if IsValid(self.Owner) then
|
||||
local vm = self.Owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
self:ResetBonePositions(vm)
|
||||
|
||||
// Init viewmodel visibility
|
||||
if (self.ShowViewModel == nil or self.ShowViewModel) then
|
||||
vm:SetColor(Color(255,255,255,255))
|
||||
else
|
||||
// we set the alpha to 1 instead of 0 because else ViewModelDrawn stops being called
|
||||
vm:SetColor(Color(255,255,255,1))
|
||||
// ^ stopped working in GMod 13 because you have to do Entity:SetRenderMode(1) for translucency to kick in
|
||||
// however for some reason the view model resets to render mode 0 every frame so we just apply a debug material to prevent it from drawing
|
||||
vm:SetMaterial("Debug/hsv")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
if self.Initialize2 then self:Initialize2() end
|
||||
end
|
||||
|
||||
function SWEP:PrimaryAttack()
|
||||
if SERVER then
|
||||
cigaUpdate(self.Owner, self.cigaID)
|
||||
end
|
||||
self.Weapon:SetNextPrimaryFire(CurTime() + 0.1)
|
||||
end
|
||||
|
||||
function SWEP:Reload()
|
||||
|
||||
end
|
||||
|
||||
function SWEP:Holster()
|
||||
if SERVER and IsValid(self.Owner) then
|
||||
Releaseciga(self.Owner)
|
||||
end
|
||||
|
||||
if CLIENT and IsValid(self.Owner) then
|
||||
local vm = self.Owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
self:ResetBonePositions(vm)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
SWEP.OnDrop = SWEP.Holster
|
||||
SWEP.OnRemove = SWEP.Holster
|
||||
@@ -0,0 +1,17 @@
|
||||
if CLIENT then
|
||||
include('weapon_ciga/cl_init.lua')
|
||||
else
|
||||
include('weapon_ciga/shared.lua')
|
||||
end
|
||||
|
||||
function SWEP:SecondaryAttack()
|
||||
end
|
||||
|
||||
SWEP.PrintName = "Camel"
|
||||
|
||||
SWEP.ViewModel = "models/oldcigshib.mdl"
|
||||
SWEP.WorldModel = "models/oldcigshib.mdl"
|
||||
|
||||
SWEP.cigaID = 3
|
||||
|
||||
SWEP.cigaAccentColor = Vector(1,1,1.1)
|
||||
@@ -0,0 +1,89 @@
|
||||
if CLIENT then
|
||||
include('weapon_ciga/cl_init.lua')
|
||||
else
|
||||
include('weapon_ciga/shared.lua')
|
||||
end
|
||||
|
||||
SWEP.PrintName = "Newport"
|
||||
|
||||
SWEP.cigaAccentColor = nil
|
||||
|
||||
SWEP.cigaID = 4
|
||||
SWEP.ViewModel = "models/ciga.mdl"
|
||||
SWEP.WorldModel = "models/ciga.mdl"
|
||||
|
||||
--Add your own flavors here, obviously
|
||||
JuicycigaJuices = {
|
||||
{name = "without filter", color = Color(40,40,40,255)},
|
||||
{name = "with filter", color = Color(210,180,140,255)},
|
||||
}
|
||||
|
||||
if SERVER then
|
||||
function SWEP:Initialize2()
|
||||
self.juiceID = 0
|
||||
timer.Simple(0.1, function() SendcigaJuice(self, JuicycigaJuices[self.juiceID+1]) end)
|
||||
end
|
||||
|
||||
util.AddNetworkString("cigaTankColor")
|
||||
util.AddNetworkString("cigaMessage")
|
||||
end
|
||||
|
||||
function SWEP:SecondaryAttack()
|
||||
if SERVER then
|
||||
if not self.juiceID then self.juiceID = 0 end
|
||||
self.juiceID = (self.juiceID + 1) % (#JuicycigaJuices)
|
||||
SendcigaJuice(self, JuicycigaJuices[self.juiceID+1])
|
||||
|
||||
--Client hook isn't called in singleplayer
|
||||
if game.SinglePlayer() then self.Owner:SendLua([[surface.PlaySound("weapons/smg1/switch_single.wav")]]) end
|
||||
else
|
||||
if IsFirstTimePredicted() then
|
||||
surface.PlaySound("weapons/smg1/switch_single.wav")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
function SendcigaJuice(ent, tab)
|
||||
local col = tab.color
|
||||
if col then
|
||||
local min = math.min(col.r,col.g,col.b)*0.8
|
||||
col = (Vector(col.r-min, col.g-min, col.b-min)*1.0)/255.0
|
||||
else
|
||||
col = Vector(-1,-1,-1)
|
||||
end
|
||||
net.Start("cigaTankColor")
|
||||
net.WriteEntity(ent)
|
||||
net.WriteVector(col)
|
||||
net.Broadcast()
|
||||
|
||||
if IsValid(ent.Owner) then
|
||||
net.Start("cigaMessage")
|
||||
net.WriteString("Loaded "..tab.name.."")
|
||||
net.Send(ent.Owner)
|
||||
end
|
||||
end
|
||||
else
|
||||
net.Receive("cigaTankColor", function()
|
||||
local ent = net.ReadEntity()
|
||||
local col = net.ReadVector()
|
||||
if IsValid(ent) then ent.cigaTankColor = col end
|
||||
end)
|
||||
|
||||
cigaMessageDisplay = ""
|
||||
cigaMessageDisplayTime = 0
|
||||
|
||||
net.Receive("cigaMessage", function()
|
||||
cigaMessageDisplay = net.ReadString()
|
||||
cigaMessageDisplayTime = CurTime()
|
||||
end)
|
||||
|
||||
hook.Add("HUDPaint", "cigaDrawJuiceMessage", function()
|
||||
local alpha = math.Clamp((cigaMessageDisplayTime+3-CurTime())*1.5,0,1)
|
||||
if alpha == 0 then return end
|
||||
|
||||
surface.SetFont("Trebuchet24")
|
||||
local w,h = surface.GetTextSize(cigaMessageDisplay)
|
||||
draw.WordBox(8, ((ScrW() - w)/2)-8, ScrH() - (h + 24), cigaMessageDisplay, "Trebuchet24", Color(0,0,0,128*alpha), Color(255,255,255,255*alpha))
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,830 @@
|
||||
CreateConVar( "lordi_sledge_break_doors", "1", { FCVAR_REPLICATED, FCVAR_NOTIFY }, "Should people be able to break down doors using the sledgehammer? 1 for true, 0 for false" )
|
||||
CreateConVar( "lordi_sledge_break_doors_chance", "5", { FCVAR_REPLICATED, FCVAR_NOTIFY }, "What should the 1/<command> chance be before a door breaks? The higher this is, the harder it is. 1 makes it break immediadly" )
|
||||
|
||||
SWEP.WElements = {
|
||||
["sledge"] = { type = "Model", model = "models/weapons/lordi/c_sledgehammer.mdl", bone = "ValveBiped.Bip01_R_Hand", rel = "", pos = Vector(-16.33, -3.294, -16.605), angle = Angle(8.395, 0, -115.988), size = Vector(1, 1, 1), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} }
|
||||
}
|
||||
|
||||
SWEP.ViewModelFOV = 70
|
||||
SWEP.ViewModel = "models/weapons/lordi/c_sledgehammer.mdl"
|
||||
SWEP.WorldModel = "models/weapons/w_crowbar.mdl"
|
||||
SWEP.UseHands = true
|
||||
SWEP.AutoSwitchTo = true
|
||||
SWEP.Slot = 0
|
||||
SWEP.HoldType = "passive"
|
||||
SWEP.Spawnable = true
|
||||
SWEP.AdminSpawnable = false
|
||||
SWEP.DrawCrosshair = false
|
||||
SWEP.Category = "[FT] Специальное Оружие"
|
||||
SWEP.SlotPos = 0
|
||||
SWEP.DrawAmmo = true
|
||||
SWEP.PrintName = "Sledgehammer"
|
||||
SWEP.Author = "LordiAnders"
|
||||
SWEP.Instructions = "Bash heads in! Or if they are afar, throw the thing after them!"
|
||||
SWEP.ShowViewModel = true
|
||||
SWEP.ShowWorldModel = true
|
||||
|
||||
SWEP.Primary.ClipSize = -1
|
||||
SWEP.Primary.DefaultClip = -1
|
||||
SWEP.Primary.Ammo = "none"
|
||||
SWEP.Primary.Automatic = true
|
||||
|
||||
SWEP.Secondary.ClipSize = -1
|
||||
SWEP.Secondary.Ammo = "none"
|
||||
SWEP.Secondary.Automatic = true
|
||||
|
||||
SWEP.AboutToSwing = false
|
||||
SWEP.IsSwinging = false
|
||||
|
||||
SWEP.AboutToSwing2 = false
|
||||
SWEP.IsSwinging2 = false
|
||||
|
||||
if CLIENT then
|
||||
SWEP.WepSelectIcon = surface.GetTextureID("vgui/entities/weapon_lordi_sledgehammer")
|
||||
killicon.Add("weapon_lordi_sledgehammer","vgui/entities/weapon_lordi_sledgehammer",Color(255,255,255,255))
|
||||
killicon.Add("ent_lordi_sledgehammer","vgui/entities/weapon_lordi_sledgehammer",Color(255,255,255,255))
|
||||
SWEP.BounceWeaponIcon = false
|
||||
end
|
||||
|
||||
function SWEP:VerifyAndSet(bone,data)
|
||||
if not IsValid(self.Owner) then return end
|
||||
local bone = self.Owner:LookupBone(bone)
|
||||
if bone then
|
||||
self.Owner:ManipulateBoneAngles(bone,data)
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:SetPassiveHoldType()
|
||||
if not IsValid(self.Owner) then return end
|
||||
if CLIENT then return end
|
||||
|
||||
self:SetHoldType("passive")
|
||||
|
||||
self:VerifyAndSet('ValveBiped.Bip01_R_UpperArm',Angle(13.8940719901227,12.334109775164,14.597405385818))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_R_Hand',Angle(17,30,9))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_Forearm',Angle(-16.224373259067,-24.886877720213,7.8929222269922))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_UpperArm',Angle(-32.017127057138,-35.637168682527,16))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_Hand',Angle(0.17154435310358,0,0))
|
||||
end
|
||||
|
||||
function SWEP:SetMelee2HoldType()
|
||||
if not IsValid(self.Owner) then return end
|
||||
if CLIENT then return end
|
||||
|
||||
self:SetHoldType("melee2")
|
||||
|
||||
self:VerifyAndSet('ValveBiped.Bip01_R_UpperArm',Angle(0,0,0))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_R_Hand',Angle(0,0,0))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_Forearm',Angle(0,0,0))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_UpperArm',Angle(0,0,0))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_Hand',Angle(0,0,0))
|
||||
end
|
||||
|
||||
function SWEP:Deploy()
|
||||
self.Weapon:SendWeaponAnim(ACT_VM_DRAW)
|
||||
self:SetDeploySpeed(self.Owner:GetViewModel():SequenceDuration())
|
||||
self.Weapon:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() )
|
||||
self.Weapon:SetNextSecondaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() )
|
||||
|
||||
self.AboutToSwing = true --Just to disable holstering
|
||||
|
||||
self:SetPassiveHoldType()
|
||||
|
||||
if SERVER then
|
||||
timer.Simple(0.9,function()
|
||||
if not self:IsValid() then return end
|
||||
self:EmitSound("npc/combine_soldier/gear6.wav")
|
||||
self.Owner:ViewPunch( Angle(-2,0,1) )
|
||||
end)
|
||||
timer.Simple(2,function()
|
||||
if not self:IsValid() then return end
|
||||
self.Owner:ViewPunch( Angle(5,0,-5) )
|
||||
self:EmitSound("physics/flesh/flesh_impact_hard2.wav",75,180)
|
||||
self.AboutToSwing = false
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:Think()
|
||||
if self.IsSwinging and not self.Owner:KeyDown(IN_ATTACK) then
|
||||
self:PrimaryAttack()
|
||||
elseif self.IsSwinging2 and not self.Owner:KeyDown(IN_ATTACK2) then
|
||||
self:SecondaryAttack()
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:PrimaryAttack()
|
||||
if self.IsSwinging2 or self.AboutToSwing2 then return end
|
||||
if not self.IsSwinging then
|
||||
self.AboutToSwing = true
|
||||
self:SetMelee2HoldType()
|
||||
self.Weapon:SetNextPrimaryFire( CurTime() + 5000 )
|
||||
self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
|
||||
timer.Simple(self.Owner:GetViewModel():SequenceDuration(),function()
|
||||
self.AboutToSwing = false
|
||||
self.IsSwinging = true
|
||||
end)
|
||||
end
|
||||
|
||||
if self.IsSwinging then
|
||||
--Trace shit from weapon_fists.lua packed with Gmod
|
||||
self.Owner:SetAnimation( PLAYER_ATTACK1 )
|
||||
timer.Simple(0.25,function() if not self:IsValid() then return end self:SetPassiveHoldType() end)
|
||||
local trace = util.TraceLine( {
|
||||
start = self.Owner:GetShootPos(),
|
||||
endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 105,
|
||||
filter = self.Owner
|
||||
} )
|
||||
|
||||
if ( !IsValid( trace.Entity ) ) then
|
||||
trace = util.TraceHull( {
|
||||
start = self.Owner:GetShootPos(),
|
||||
endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 105,
|
||||
filter = self.Owner,
|
||||
mins = Vector( -10, -10, -8 ),
|
||||
maxs = Vector( 10, 10, 8 )
|
||||
} )
|
||||
end
|
||||
|
||||
if trace.Entity:IsValid() or trace.HitWorld then
|
||||
self.Weapon:SendWeaponAnim(ACT_VM_HITKILL)
|
||||
self.Owner:ViewPunch( Angle(-10,0,0) )
|
||||
timer.Simple(0.1,function()
|
||||
if not self:IsValid() then return end
|
||||
self.Owner:ViewPunch( Angle(15,0,0) )
|
||||
end)
|
||||
if trace.Entity:IsValid() then
|
||||
if trace.Entity:IsPlayer() or trace.Entity:IsNPC() then
|
||||
if SERVER then trace.Entity:EmitSound("physics/body/body_medium_break"..math.random(2,4)..".wav",75,math.random(70,90)) end
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin(trace.HitPos)
|
||||
effectdata:SetEntity(trace.Entity)
|
||||
util.Effect( "BloodImpact", effectdata )
|
||||
elseif trace.Entity:GetClass() == "prop_door_rotating" and GetConVarNumber( "lordi_sledge_break_doors" ) == 1 then
|
||||
if SERVER and math.random(1,GetConVarNumber( "lordi_sledge_break_doors_chance" )) == 1 then
|
||||
trace.Entity:Fire("unlock")
|
||||
trace.Entity:Fire("open")
|
||||
|
||||
trace.Entity:SetNotSolid(true)
|
||||
trace.Entity:SetNoDraw(true)
|
||||
|
||||
--Bit of madcow stuff here... :L
|
||||
|
||||
local ent = ents.Create("prop_physics")
|
||||
|
||||
trace.Entity:EmitSound("physics/wood/wood_furniture_break1.wav")
|
||||
|
||||
ent:SetPos(trace.Entity:GetPos())
|
||||
ent:SetAngles(trace.Entity:GetAngles())
|
||||
ent:SetModel(trace.Entity:GetModel())
|
||||
|
||||
if trace.Entity:GetSkin() then
|
||||
ent:SetSkin(trace.Entity:GetSkin())
|
||||
end
|
||||
|
||||
ent:Spawn()
|
||||
|
||||
ent:GetPhysicsObject():ApplyForceCenter( self.Owner:GetAimVector() * 10000 )
|
||||
|
||||
timer.Simple(25,function()
|
||||
if ent:IsValid() then
|
||||
ent:Remove()
|
||||
end
|
||||
trace.Entity:SetNotSolid(false)
|
||||
trace.Entity:SetNoDraw(false)
|
||||
end)
|
||||
elseif SERVER then
|
||||
trace.Entity:EmitSound("physics/wood/wood_box_break"..math.random(1,2)..".wav",75,math.random(50,150))
|
||||
end
|
||||
end
|
||||
if SERVER then trace.Entity:TakeDamage(math.random(70,120),self.Owner) end
|
||||
end
|
||||
if trace.HitWorld then
|
||||
local trace = self.Owner:GetEyeTrace()
|
||||
|
||||
if self.Owner:GetShootPos():Distance(trace.HitPos) <= 105 then
|
||||
util.Decal("Impact.Sand",trace.HitPos + trace.HitNormal,trace.HitPos - trace.HitNormal)
|
||||
end
|
||||
end
|
||||
if SERVER then timer.Simple(0,function() if not self:IsValid() then return end self:EmitSound("physics/metal/metal_canister_impact_hard"..math.random(1,3)..".wav",75,math.random(90,110)) end) end
|
||||
else
|
||||
self.Weapon:SendWeaponAnim(ACT_VM_MISSCENTER)
|
||||
if SERVER then timer.Simple(0,function() if not self:IsValid() then return end self:EmitSound("npc/zombie/claw_miss1.wav",75,60) end) end
|
||||
self.Owner:ViewPunch( Angle(-10,0,0) )
|
||||
timer.Simple(0.1,function()
|
||||
if not self:IsValid() then return end
|
||||
self.Owner:ViewPunch( Angle(30,0,0) )
|
||||
end)
|
||||
end
|
||||
self.IsSwinging = false
|
||||
self.Weapon:SetNextPrimaryFire( CurTime() + 1 )
|
||||
self.Weapon:SetNextSecondaryFire( CurTime() + 1 )
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:GetViewModelPosition(pos, ang)
|
||||
if self.IsSwinging then
|
||||
pos = pos + (math.random(-1,1) / 40) * ang:Right()
|
||||
pos = pos + (math.random(-1,1) / 40) * ang:Forward()
|
||||
pos = pos + (math.random(-1,1) / 40) * ang:Up()
|
||||
end
|
||||
|
||||
return pos, ang
|
||||
end
|
||||
|
||||
function SWEP:SecondaryAttack()
|
||||
if self.IsSwinging or self.AboutToSwing then return end
|
||||
if not self.IsSwinging2 then
|
||||
self.AboutToSwing2 = true
|
||||
self:SetMelee2HoldType()
|
||||
self.Weapon:SetNextSecondaryFire( CurTime() + 5000 )
|
||||
self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
|
||||
timer.Simple(self.Owner:GetViewModel():SequenceDuration(),function()
|
||||
self.AboutToSwing2 = false
|
||||
self.IsSwinging2 = true
|
||||
end)
|
||||
end
|
||||
|
||||
if self.IsSwinging2 then
|
||||
self.IsSwinging2 = false
|
||||
self.Weapon:SendWeaponAnim(ACT_VM_MISSCENTER)
|
||||
timer.Simple(0.1,function()
|
||||
if SERVER then
|
||||
local sledge = ents.Create("ent_lordi_sledgehammer")
|
||||
sledge:SetOwner(self.Owner)
|
||||
sledge:SetPos(self.Owner:EyePos() + self.Owner:GetAimVector())
|
||||
sledge:SetAngles(self.Owner:GetAngles())
|
||||
sledge:Spawn()
|
||||
sledge:Activate()
|
||||
|
||||
sledge:GetPhysicsObject():ApplyForceCenter( self.Owner:GetAimVector() * 7000 )
|
||||
|
||||
timer.Simple(0.25,function() self.Owner:StripWeapon(self:GetClass()) end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function SWEP:Reload() end
|
||||
|
||||
function SWEP:OnRemove()
|
||||
if IsValid(self.Owner) then
|
||||
self:VerifyAndSet('ValveBiped.Bip01_R_UpperArm',Angle(0,0,0))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_R_Hand',Angle(0,0,0))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_Forearm',Angle(0,0,0))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_UpperArm',Angle(0,0,0))
|
||||
self:VerifyAndSet('ValveBiped.Bip01_L_Hand',Angle(0,0,0))
|
||||
local vm = self.Owner:GetViewModel()
|
||||
if IsValid(vm) then vm:SetMaterial("") end
|
||||
end
|
||||
if CLIENT and IsValid(self.Owner) then
|
||||
local vm = self.Owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
self:ResetBonePositions(vm)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
/********************************************************
|
||||
SWEP Construction Kit base code
|
||||
Created by Clavus
|
||||
Available for public use, thread at:
|
||||
facepunch.com/threads/1032378
|
||||
|
||||
|
||||
DESCRIPTION:
|
||||
This script is meant for experienced scripters
|
||||
that KNOW WHAT THEY ARE DOING. Don't come to me
|
||||
with basic Lua questions.
|
||||
|
||||
Just copy into your SWEP or SWEP base of choice
|
||||
and merge with your own code.
|
||||
|
||||
The SWEP.VElements, SWEP.WElements and
|
||||
SWEP.ViewModelBoneMods tables are all optional
|
||||
and only have to be visible to the client.
|
||||
********************************************************/
|
||||
|
||||
function SWEP:Initialize()
|
||||
self:SetHoldType(self.HoldType)
|
||||
|
||||
// other initialize code goes here
|
||||
|
||||
if CLIENT then
|
||||
|
||||
// Create a new table for every weapon instance
|
||||
self.VElements = table.FullCopy( self.VElements )
|
||||
self.WElements = table.FullCopy( self.WElements )
|
||||
self.ViewModelBoneMods = table.FullCopy( self.ViewModelBoneMods )
|
||||
|
||||
self:CreateModels(self.VElements) // create viewmodels
|
||||
self:CreateModels(self.WElements) // create worldmodels
|
||||
|
||||
// init view model bone build function
|
||||
if IsValid(self.Owner) then
|
||||
local vm = self.Owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
self:ResetBonePositions(vm)
|
||||
end
|
||||
|
||||
// Init viewmodel visibility
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SWEP:Holster()
|
||||
|
||||
if self.AboutToSwing or self.IsSwinging or self.IsSwinging2 or self.AboutToSwing2 then return end
|
||||
|
||||
self:OnRemove()
|
||||
|
||||
if CLIENT and IsValid(self.Owner) then
|
||||
local vm = self.Owner:GetViewModel()
|
||||
if IsValid(vm) then
|
||||
self:ResetBonePositions(vm)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
|
||||
SWEP.vRenderOrder = nil
|
||||
function SWEP:ViewModelDrawn()
|
||||
|
||||
local vm = self.Owner:GetViewModel()
|
||||
if !IsValid(vm) then return end
|
||||
|
||||
if (!self.VElements) then return end
|
||||
|
||||
self:UpdateBonePositions(vm)
|
||||
|
||||
if (!self.vRenderOrder) then
|
||||
|
||||
// we build a render order because sprites need to be drawn after models
|
||||
self.vRenderOrder = {}
|
||||
|
||||
for k, v in pairs( self.VElements ) do
|
||||
if (v.type == "Model") then
|
||||
table.insert(self.vRenderOrder, 1, k)
|
||||
elseif (v.type == "Sprite" or v.type == "Quad") then
|
||||
table.insert(self.vRenderOrder, k)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
for k, name in ipairs( self.vRenderOrder ) do
|
||||
|
||||
local v = self.VElements[name]
|
||||
if (!v) then self.vRenderOrder = nil break end
|
||||
if (v.hide) then continue end
|
||||
|
||||
local model = v.modelEnt
|
||||
local sprite = v.spriteMaterial
|
||||
|
||||
if (!v.bone) then continue end
|
||||
|
||||
local pos, ang = self:GetBoneOrientation( self.VElements, v, vm )
|
||||
|
||||
if (!pos) then continue end
|
||||
|
||||
if (v.type == "Model" and IsValid(model)) then
|
||||
|
||||
model:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z )
|
||||
ang:RotateAroundAxis(ang:Up(), v.angle.y)
|
||||
ang:RotateAroundAxis(ang:Right(), v.angle.p)
|
||||
ang:RotateAroundAxis(ang:Forward(), v.angle.r)
|
||||
|
||||
model:SetAngles(ang)
|
||||
//model:SetModelScale(v.size)
|
||||
local matrix = Matrix()
|
||||
matrix:Scale(v.size)
|
||||
model:EnableMatrix( "RenderMultiply", matrix )
|
||||
|
||||
if (v.material == "") then
|
||||
model:SetMaterial("")
|
||||
elseif (model:GetMaterial() != v.material) then
|
||||
model:SetMaterial( v.material )
|
||||
end
|
||||
|
||||
if (v.skin and v.skin != model:GetSkin()) then
|
||||
model:SetSkin(v.skin)
|
||||
end
|
||||
|
||||
if (v.bodygroup) then
|
||||
for k, v in pairs( v.bodygroup ) do
|
||||
if (model:GetBodygroup(k) != v) then
|
||||
model:SetBodygroup(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (v.surpresslightning) then
|
||||
render.SuppressEngineLighting(true)
|
||||
end
|
||||
|
||||
render.SetColorModulation(v.color.r/255, v.color.g/255, v.color.b/255)
|
||||
render.SetBlend(v.color.a/255)
|
||||
model:DrawModel()
|
||||
render.SetBlend(1)
|
||||
render.SetColorModulation(1, 1, 1)
|
||||
|
||||
if (v.surpresslightning) then
|
||||
render.SuppressEngineLighting(false)
|
||||
end
|
||||
|
||||
elseif (v.type == "Sprite" and sprite) then
|
||||
|
||||
local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z
|
||||
render.SetMaterial(sprite)
|
||||
render.DrawSprite(drawpos, v.size.x, v.size.y, v.color)
|
||||
|
||||
elseif (v.type == "Quad" and v.draw_func) then
|
||||
|
||||
local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z
|
||||
ang:RotateAroundAxis(ang:Up(), v.angle.y)
|
||||
ang:RotateAroundAxis(ang:Right(), v.angle.p)
|
||||
ang:RotateAroundAxis(ang:Forward(), v.angle.r)
|
||||
|
||||
cam.Start3D2D(drawpos, ang, v.size)
|
||||
v.draw_func( self )
|
||||
cam.End3D2D()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
SWEP.wRenderOrder = nil
|
||||
function SWEP:DrawWorldModel()
|
||||
|
||||
self:DrawModel()
|
||||
self:SetMaterial( "engine/occlusionproxy" )
|
||||
|
||||
if (!self.WElements) then return end
|
||||
|
||||
if (!self.wRenderOrder) then
|
||||
|
||||
self.wRenderOrder = {}
|
||||
|
||||
for k, v in pairs( self.WElements ) do
|
||||
if (v.type == "Model") then
|
||||
table.insert(self.wRenderOrder, 1, k)
|
||||
elseif (v.type == "Sprite" or v.type == "Quad") then
|
||||
table.insert(self.wRenderOrder, k)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if (IsValid(self.Owner)) then
|
||||
bone_ent = self.Owner
|
||||
else
|
||||
// when the weapon is dropped
|
||||
bone_ent = self
|
||||
end
|
||||
|
||||
for k, name in pairs( self.wRenderOrder ) do
|
||||
|
||||
local v = self.WElements[name]
|
||||
if (!v) then self.wRenderOrder = nil break end
|
||||
if (v.hide) then continue end
|
||||
|
||||
local pos, ang
|
||||
|
||||
if (v.bone) then
|
||||
pos, ang = self:GetBoneOrientation( self.WElements, v, bone_ent )
|
||||
else
|
||||
pos, ang = self:GetBoneOrientation( self.WElements, v, bone_ent, "ValveBiped.Bip01_R_Hand" )
|
||||
end
|
||||
|
||||
if (!pos) then continue end
|
||||
|
||||
local model = v.modelEnt
|
||||
local sprite = v.spriteMaterial
|
||||
|
||||
if (v.type == "Model" and IsValid(model)) then
|
||||
|
||||
model:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z )
|
||||
ang:RotateAroundAxis(ang:Up(), v.angle.y)
|
||||
ang:RotateAroundAxis(ang:Right(), v.angle.p)
|
||||
ang:RotateAroundAxis(ang:Forward(), v.angle.r)
|
||||
|
||||
model:SetAngles(ang)
|
||||
//model:SetModelScale(v.size)
|
||||
local matrix = Matrix()
|
||||
matrix:Scale(v.size)
|
||||
model:EnableMatrix( "RenderMultiply", matrix )
|
||||
|
||||
if (v.material == "") then
|
||||
model:SetMaterial("")
|
||||
elseif (model:GetMaterial() != v.material) then
|
||||
model:SetMaterial( v.material )
|
||||
end
|
||||
|
||||
if (v.skin and v.skin != model:GetSkin()) then
|
||||
model:SetSkin(v.skin)
|
||||
end
|
||||
|
||||
if (v.bodygroup) then
|
||||
for k, v in pairs( v.bodygroup ) do
|
||||
if (model:GetBodygroup(k) != v) then
|
||||
model:SetBodygroup(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (v.surpresslightning) then
|
||||
render.SuppressEngineLighting(true)
|
||||
end
|
||||
|
||||
render.SetColorModulation(v.color.r/255, v.color.g/255, v.color.b/255)
|
||||
render.SetBlend(v.color.a/255)
|
||||
model:DrawModel()
|
||||
render.SetBlend(1)
|
||||
render.SetColorModulation(1, 1, 1)
|
||||
|
||||
if (v.surpresslightning) then
|
||||
render.SuppressEngineLighting(false)
|
||||
end
|
||||
|
||||
elseif (v.type == "Sprite" and sprite) then
|
||||
|
||||
local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z
|
||||
render.SetMaterial(sprite)
|
||||
render.DrawSprite(drawpos, v.size.x, v.size.y, v.color)
|
||||
|
||||
elseif (v.type == "Quad" and v.draw_func) then
|
||||
|
||||
local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z
|
||||
ang:RotateAroundAxis(ang:Up(), v.angle.y)
|
||||
ang:RotateAroundAxis(ang:Right(), v.angle.p)
|
||||
ang:RotateAroundAxis(ang:Forward(), v.angle.r)
|
||||
|
||||
cam.Start3D2D(drawpos, ang, v.size)
|
||||
v.draw_func( self )
|
||||
cam.End3D2D()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SWEP:GetBoneOrientation( basetab, tab, ent, bone_override )
|
||||
|
||||
local bone, pos, ang
|
||||
if (tab.rel and tab.rel != "") then
|
||||
|
||||
local v = basetab[tab.rel]
|
||||
|
||||
if (!v) then return end
|
||||
|
||||
// Technically, if there exists an element with the same name as a bone
|
||||
// you can get in an infinite loop. Let's just hope nobody's that stupid.
|
||||
pos, ang = self:GetBoneOrientation( basetab, v, ent )
|
||||
|
||||
if (!pos) then return end
|
||||
|
||||
pos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z
|
||||
ang:RotateAroundAxis(ang:Up(), v.angle.y)
|
||||
ang:RotateAroundAxis(ang:Right(), v.angle.p)
|
||||
ang:RotateAroundAxis(ang:Forward(), v.angle.r)
|
||||
|
||||
else
|
||||
|
||||
bone = ent:LookupBone(bone_override or tab.bone)
|
||||
|
||||
if (!bone) then return end
|
||||
|
||||
pos, ang = Vector(0,0,0), Angle(0,0,0)
|
||||
local m = ent:GetBoneMatrix(bone)
|
||||
if (m) then
|
||||
pos, ang = m:GetTranslation(), m:GetAngles()
|
||||
end
|
||||
|
||||
if (IsValid(self.Owner) and self.Owner:IsPlayer() and
|
||||
ent == self.Owner:GetViewModel() and self.ViewModelFlip) then
|
||||
ang.r = -ang.r // Fixes mirrored models
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return pos, ang
|
||||
end
|
||||
|
||||
function SWEP:CreateModels( tab )
|
||||
|
||||
if (!tab) then return end
|
||||
|
||||
// Create the clientside models here because Garry says we can't do it in the render hook
|
||||
for k, v in pairs( tab ) do
|
||||
if (v.type == "Model" and v.model and v.model != "" and (!IsValid(v.modelEnt) or v.createdModel != v.model) and
|
||||
string.find(v.model, ".mdl") and file.Exists (v.model, "GAME") ) then
|
||||
|
||||
v.modelEnt = ClientsideModel(v.model, RENDER_GROUP_VIEW_MODEL_OPAQUE)
|
||||
if (IsValid(v.modelEnt)) then
|
||||
v.modelEnt:SetPos(self:GetPos())
|
||||
v.modelEnt:SetAngles(self:GetAngles())
|
||||
v.modelEnt:SetParent(self)
|
||||
v.modelEnt:SetNoDraw(true)
|
||||
v.createdModel = v.model
|
||||
else
|
||||
v.modelEnt = nil
|
||||
end
|
||||
|
||||
elseif (v.type == "Sprite" and v.sprite and v.sprite != "" and (!v.spriteMaterial or v.createdSprite != v.sprite)
|
||||
and file.Exists ("materials/"..v.sprite..".vmt", "GAME")) then
|
||||
|
||||
local name = v.sprite.."-"
|
||||
local params = { ["$basetexture"] = v.sprite }
|
||||
// make sure we create a unique name based on the selected options
|
||||
local tocheck = { "nocull", "additive", "vertexalpha", "vertexcolor", "ignorez" }
|
||||
for i, j in pairs( tocheck ) do
|
||||
if (v[j]) then
|
||||
params["$"..j] = 1
|
||||
name = name.."1"
|
||||
else
|
||||
name = name.."0"
|
||||
end
|
||||
end
|
||||
|
||||
v.createdSprite = v.sprite
|
||||
v.spriteMaterial = CreateMaterial(name,"UnlitGeneric",params)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local allbones
|
||||
local hasGarryFixedBoneScalingYet = false
|
||||
|
||||
function SWEP:UpdateBonePositions2(vm)
|
||||
|
||||
if self.GrenadeThrowBoneMods then
|
||||
|
||||
if (!vm:GetBoneCount()) then return end
|
||||
|
||||
local loopthrough = self.GrenadeThrowBoneMods
|
||||
if (!hasGarryFixedBoneScalingYet) then
|
||||
allbones = {}
|
||||
for i=0, vm:GetBoneCount() do
|
||||
local bonename = vm:GetBoneName(i)
|
||||
if (self.GrenadeThrowBoneMods[bonename]) then
|
||||
allbones[bonename] = self.GrenadeThrowBoneMods[bonename]
|
||||
else
|
||||
allbones[bonename] = {
|
||||
scale = Vector(1,1,1),
|
||||
pos = Vector(0,0,0),
|
||||
angle = Angle(0,0,0)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
loopthrough = allbones
|
||||
end
|
||||
|
||||
for k, v in pairs( loopthrough ) do
|
||||
local bone = vm:LookupBone(k)
|
||||
if (!bone) then continue end
|
||||
|
||||
local s = Vector(v.scale.x,v.scale.y,v.scale.z)
|
||||
local p = Vector(v.pos.x,v.pos.y,v.pos.z)
|
||||
local ms = Vector(1,1,1)
|
||||
if (!hasGarryFixedBoneScalingYet) then
|
||||
local cur = vm:GetBoneParent(bone)
|
||||
while(cur >= 0) do
|
||||
local pscale = loopthrough[vm:GetBoneName(cur)].scale
|
||||
ms = ms * pscale
|
||||
cur = vm:GetBoneParent(cur)
|
||||
end
|
||||
end
|
||||
|
||||
s = s * ms
|
||||
|
||||
if vm:GetManipulateBoneScale(bone) != s then
|
||||
vm:ManipulateBoneScale( bone, s )
|
||||
end
|
||||
if vm:GetManipulateBoneAngles(bone) != v.angle then
|
||||
vm:ManipulateBoneAngles( bone, v.angle )
|
||||
end
|
||||
if vm:GetManipulateBonePosition(bone) != p then
|
||||
vm:ManipulateBonePosition( bone, p )
|
||||
end
|
||||
end
|
||||
else
|
||||
self:ResetBonePositions(vm)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SWEP:UpdateBonePositions(vm)
|
||||
|
||||
if self.ViewModelBoneMods then
|
||||
|
||||
if (!vm:GetBoneCount()) then return end
|
||||
|
||||
// !! WORKAROUND !! //
|
||||
// We need to check all model names :/
|
||||
local loopthrough = self.ViewModelBoneMods
|
||||
if (!hasGarryFixedBoneScalingYet) then
|
||||
allbones = {}
|
||||
for i=0, vm:GetBoneCount() do
|
||||
local bonename = vm:GetBoneName(i)
|
||||
if (self.ViewModelBoneMods[bonename]) then
|
||||
allbones[bonename] = self.ViewModelBoneMods[bonename]
|
||||
else
|
||||
allbones[bonename] = {
|
||||
scale = Vector(1,1,1),
|
||||
pos = Vector(0,0,0),
|
||||
angle = Angle(0,0,0)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
loopthrough = allbones
|
||||
end
|
||||
// !! ----------- !! //
|
||||
|
||||
for k, v in pairs( loopthrough ) do
|
||||
local bone = vm:LookupBone(k)
|
||||
if (!bone) then continue end
|
||||
|
||||
// !! WORKAROUND !! //
|
||||
local s = Vector(v.scale.x,v.scale.y,v.scale.z)
|
||||
local p = Vector(v.pos.x,v.pos.y,v.pos.z)
|
||||
local ms = Vector(1,1,1)
|
||||
if (!hasGarryFixedBoneScalingYet) then
|
||||
local cur = vm:GetBoneParent(bone)
|
||||
while(cur >= 0) do
|
||||
local pscale = loopthrough[vm:GetBoneName(cur)].scale
|
||||
ms = ms * pscale
|
||||
cur = vm:GetBoneParent(cur)
|
||||
end
|
||||
end
|
||||
|
||||
s = s * ms
|
||||
// !! ----------- !! //
|
||||
|
||||
if vm:GetManipulateBoneScale(bone) != s then
|
||||
vm:ManipulateBoneScale( bone, s )
|
||||
end
|
||||
if vm:GetManipulateBoneAngles(bone) != v.angle then
|
||||
vm:ManipulateBoneAngles( bone, v.angle )
|
||||
end
|
||||
if vm:GetManipulateBonePosition(bone) != p then
|
||||
vm:ManipulateBonePosition( bone, p )
|
||||
end
|
||||
end
|
||||
else
|
||||
self:ResetBonePositions(vm)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SWEP:ResetBonePositions(vm)
|
||||
|
||||
if (!vm:GetBoneCount()) then return end
|
||||
for i=0, vm:GetBoneCount() do
|
||||
vm:ManipulateBoneScale( i, Vector(1, 1, 1) )
|
||||
vm:ManipulateBoneAngles( i, Angle(0, 0, 0) )
|
||||
vm:ManipulateBonePosition( i, Vector(0, 0, 0) )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
/**************************
|
||||
Global utility code
|
||||
**************************/
|
||||
|
||||
// Fully copies the table, meaning all tables inside this table are copied too and so on (normal table.Copy copies only their reference).
|
||||
// Does not copy entities of course, only copies their reference.
|
||||
// WARNING: do not use on tables that contain themselves somewhere down the line or you'll get an infinite loop
|
||||
function table.FullCopy( tab )
|
||||
|
||||
if (!tab) then return nil end
|
||||
|
||||
local res = {}
|
||||
for k, v in pairs( tab ) do
|
||||
if (type(v) == "table") then
|
||||
res[k] = table.FullCopy(v) // recursion ho!
|
||||
elseif (type(v) == "Vector") then
|
||||
res[k] = Vector(v.x, v.y, v.z)
|
||||
elseif (type(v) == "Angle") then
|
||||
res[k] = Angle(v.p, v.y, v.r)
|
||||
else
|
||||
res[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return res
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
if SERVER then
|
||||
game.SetAmmoMax("rpg_round", 1)
|
||||
game.SetAmmoMax("PanzerFaust3 Rocket", 1)
|
||||
end
|
||||
2
garrysmod/gamemodes/militaryrp/gamemode/cl_init.lua
Normal file
2
garrysmod/gamemodes/militaryrp/gamemode/cl_init.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
DeriveGamemode("helix")
|
||||
9
garrysmod/gamemodes/militaryrp/gamemode/init.lua
Normal file
9
garrysmod/gamemodes/militaryrp/gamemode/init.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
AddCSLuaFile("cl_init.lua")
|
||||
DeriveGamemode("helix")
|
||||
|
||||
if SERVER then
|
||||
-- Limit ammo for anti-tank rockets to 1
|
||||
game.SetAmmoMax("rpg_round", 1)
|
||||
game.SetAmmoMax("PanzerFaust3 Rocket", 1)
|
||||
end
|
||||
8
garrysmod/gamemodes/militaryrp/militaryrp.txt
Normal file
8
garrysmod/gamemodes/militaryrp/militaryrp.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
"exp_surv"
|
||||
{
|
||||
"base" "helix"
|
||||
"title" "FT Team VNU"
|
||||
"author" "nebulous"
|
||||
"menusystem" "1"
|
||||
}
|
||||
199
garrysmod/gamemodes/militaryrp/plugins/admin_magaz/cl_plugin.lua
Normal file
199
garrysmod/gamemodes/militaryrp/plugins/admin_magaz/cl_plugin.lua
Normal file
@@ -0,0 +1,199 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
surface.CreateFont("ixAdminShopTitle", { font = "Montserrat", size = 28, weight = 900, antialias = true })
|
||||
surface.CreateFont("ixAdminShopTab", { font = "Montserrat", size = 18, weight = 700, antialias = true })
|
||||
surface.CreateFont("ixAdminShopItem", { font = "Montserrat", size = 20, weight = 800, antialias = true })
|
||||
surface.CreateFont("ixAdminShopDesc", { font = "Montserrat", size = 14, weight = 500, antialias = true })
|
||||
surface.CreateFont("ixAdminShopPrice", { font = "Montserrat", size = 18, weight = 900, antialias = true })
|
||||
surface.CreateFont("ixAdminShopSmall", { font = "Montserrat", size = 14, weight = 600, antialias = true })
|
||||
surface.CreateFont("ixAdminShopBold", { font = "Montserrat", size = 16, weight = 800, antialias = true })
|
||||
|
||||
-- Modern Color Palette #00431c
|
||||
local COLOR_BASE = Color(0, 67, 28)
|
||||
local COLOR_BG = Color(15, 18, 16, 250)
|
||||
local COLOR_CARD = Color(22, 26, 24, 255)
|
||||
local COLOR_CARD_LIGHT = Color(30, 36, 32, 255)
|
||||
local COLOR_TEXT = Color(240, 245, 240)
|
||||
local COLOR_TEXT_DIM = Color(140, 150, 145)
|
||||
local COLOR_ACCENT = Color(0, 140, 60)
|
||||
local COLOR_DANGER = Color(200, 50, 50)
|
||||
local COLOR_WARN = Color(200, 150, 0)
|
||||
|
||||
function PLUGIN:WrapText(text, width, font)
|
||||
surface.SetFont(font)
|
||||
local words = string.Explode(" ", text)
|
||||
local lines = {}
|
||||
local currentLine = ""
|
||||
|
||||
for _, word in ipairs(words) do
|
||||
local testLine = currentLine == "" and word or currentLine .. " " .. word
|
||||
local w, _ = surface.GetTextSize(testLine)
|
||||
|
||||
if w > width then
|
||||
table.insert(lines, currentLine)
|
||||
currentLine = word
|
||||
else
|
||||
currentLine = testLine
|
||||
end
|
||||
end
|
||||
|
||||
if currentLine != "" then
|
||||
table.insert(lines, currentLine)
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetSize(ScrW() * 0.7, ScrH() * 0.75)
|
||||
self:Center()
|
||||
self:MakePopup()
|
||||
self:SetTitle("")
|
||||
self:ShowCloseButton(false)
|
||||
self.currentView = "shop"
|
||||
|
||||
self:SetAlpha(0)
|
||||
self:AlphaTo(255, 0.2, 0)
|
||||
|
||||
self.startTime = SysTime()
|
||||
|
||||
self.Paint = function(s, w, h)
|
||||
Derma_DrawBackgroundBlur(s, s.startTime)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG)
|
||||
surface.SetDrawColor(COLOR_BASE.r, COLOR_BASE.g, COLOR_BASE.b, 150)
|
||||
surface.SetMaterial(Material("vgui/gradient-u"))
|
||||
surface.DrawTexturedRect(0, 0, w, 80)
|
||||
surface.SetDrawColor(COLOR_BASE)
|
||||
surface.DrawRect(0, 80, w, 2)
|
||||
|
||||
local title = self.currentView == "shop" and "АДМИН МАГАЗИН" or (self.currentView == "inventory" and "МОЙ ИНВЕНТАРЬ" or "УПРАВЛЕНИЕ АДМИНИСТРАТОРАМИ")
|
||||
draw.SimpleText(title, "ixAdminShopTitle", 40, 40, COLOR_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
local points = LocalPlayer():GetAdminPoints()
|
||||
draw.SimpleText("Текущий баланс:", "ixAdminShopSmall", w * 0.45, 40, COLOR_TEXT_DIM, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText(points .. " Очков", "ixAdminShopItem", w * 0.45 + 10, 40, COLOR_ACCENT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
local closeBtn = self:Add("DButton")
|
||||
closeBtn:SetSize(40, 40)
|
||||
closeBtn:SetPos(self:GetWide() - 50, 20)
|
||||
closeBtn:SetText("✕")
|
||||
closeBtn:SetFont("ixAdminShopTitle")
|
||||
closeBtn:SetTextColor(COLOR_TEXT_DIM)
|
||||
closeBtn.Paint = nil
|
||||
closeBtn.DoClick = function()
|
||||
self:AlphaTo(0, 0.2, 0, function() self:Close() end)
|
||||
end
|
||||
closeBtn.OnCursorEntered = function(s) s:SetTextColor(COLOR_DANGER) end
|
||||
closeBtn.OnCursorExited = function(s) s:SetTextColor(COLOR_TEXT_DIM) end
|
||||
|
||||
self.nav = self:Add("Panel")
|
||||
self.nav:SetSize(450, 40)
|
||||
self.nav:SetPos(self:GetWide() - 520, 20)
|
||||
|
||||
local function CreateNavBtn(text, view, x, activeColor)
|
||||
local btn = self.nav:Add("DButton")
|
||||
btn:SetSize(140, 40)
|
||||
btn:SetPos(x, 0)
|
||||
btn:SetText(text)
|
||||
btn:SetFont("ixAdminShopBold")
|
||||
btn:SetTextColor(COLOR_TEXT)
|
||||
btn.Paint = function(s, w, h)
|
||||
if self.currentView == view then
|
||||
draw.RoundedBox(4, 0, 0, w, h, activeColor)
|
||||
elseif s:IsHovered() then
|
||||
draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 10))
|
||||
end
|
||||
end
|
||||
btn.DoClick = function()
|
||||
surface.PlaySound("ui/buttonclick.wav")
|
||||
self.currentView = view
|
||||
self:Refresh()
|
||||
end
|
||||
return btn
|
||||
end
|
||||
|
||||
CreateNavBtn("Магазин", "shop", 0, COLOR_BASE)
|
||||
|
||||
self.content = self:Add("DScrollPanel")
|
||||
self.content:Dock(FILL)
|
||||
self.content:DockMargin(40, 100, 40, 40)
|
||||
|
||||
local sbar = self.content:GetVBar()
|
||||
sbar:SetWide(8)
|
||||
sbar:SetHideButtons(true)
|
||||
sbar.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, Color(0, 0, 0, 100))
|
||||
end
|
||||
sbar.btnGrip.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 2, 0, w-4, h, s:IsHovered() and COLOR_ACCENT or COLOR_BASE)
|
||||
end
|
||||
|
||||
self:Refresh()
|
||||
end
|
||||
|
||||
function PANEL:Refresh()
|
||||
self.content:Clear()
|
||||
|
||||
if (self.currentView == "shop") then
|
||||
local layout = self.content:Add("DIconLayout")
|
||||
layout:Dock(TOP)
|
||||
layout:SetSpaceX(20)
|
||||
layout:SetSpaceY(20)
|
||||
|
||||
for id, item in pairs(PLUGIN.Items) do
|
||||
local card = layout:Add("DPanel")
|
||||
card:SetSize(250, 340)
|
||||
card.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_CARD)
|
||||
surface.SetDrawColor(COLOR_CARD_LIGHT)
|
||||
surface.SetMaterial(Material("vgui/gradient-d"))
|
||||
surface.DrawTexturedRect(0, 0, w, 140)
|
||||
|
||||
draw.SimpleText(item.name, "ixAdminShopItem", w/2, 160, COLOR_TEXT, TEXT_ALIGN_CENTER)
|
||||
|
||||
local descLines = PLUGIN:WrapText(item.desc, w - 30, "ixAdminShopDesc")
|
||||
for i, line in ipairs(descLines) do
|
||||
if (i > 4) then break end
|
||||
draw.SimpleText(line, "ixAdminShopDesc", w/2, 185 + (i-1)*18, COLOR_TEXT_DIM, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
local icon = card:Add("DPanel")
|
||||
icon:SetSize(60, 60)
|
||||
icon:SetPos(250/2 - 30, 40)
|
||||
icon.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG)
|
||||
surface.SetDrawColor(item.color or COLOR_ACCENT)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
|
||||
local buy = card:Add("DButton")
|
||||
buy:SetSize(210, 45)
|
||||
buy:SetPos(20, 275)
|
||||
buy:SetText("Купить за " .. item.price .. " Очков")
|
||||
buy:SetFont("ixAdminShopBold")
|
||||
buy:SetTextColor(COLOR_TEXT)
|
||||
buy.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and COLOR_ACCENT or COLOR_BASE)
|
||||
end
|
||||
buy.DoClick = function()
|
||||
surface.PlaySound("ui/buttonclick.wav")
|
||||
net.Start("ixAdminShopBuy")
|
||||
net.WriteString(id)
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixAdminShop", PANEL, "DFrame")
|
||||
|
||||
net.Receive("ixAdminShopOpen", function()
|
||||
if (IsValid(ixAdminShop)) then
|
||||
ixAdminShop:Close()
|
||||
end
|
||||
ixAdminShop = vgui.Create("ixAdminShop")
|
||||
end)
|
||||
@@ -0,0 +1,75 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
PLUGIN.name = "Admin Shop"
|
||||
PLUGIN.author = "Scripty"
|
||||
PLUGIN.description = "Adds an admin shop with a unique currency 'Points', inventory, and admin panel."
|
||||
|
||||
PLUGIN.Items = {
|
||||
["remove_warn"] = {
|
||||
name = "Снятие выговора",
|
||||
desc = "Снимает один активный выговор с вашего аккаунта. Выговор будет удален из базы данных, и это отобразится в истории.",
|
||||
price = 150,
|
||||
category = "Other",
|
||||
icon = "icon16/error_delete.png",
|
||||
oneTime = true
|
||||
},
|
||||
["tacrp_ex_m4a1"] = {
|
||||
name = "Оружие: HK416",
|
||||
desc = "Мощная штурмовая винтовка HK416. Доступ на 10 дней. Оружие выдается через инвентарь.",
|
||||
price = 50,
|
||||
category = "Weapons",
|
||||
duration = 10,
|
||||
class = "tacrp_ex_m4a1",
|
||||
color = Color(30, 144, 255)
|
||||
},
|
||||
["tacrp_mg4"] = {
|
||||
name = "Оружие: M249",
|
||||
desc = "Тяжелый пулемет M249. Доступ на 15 дней. Оружие выдается через инвентарь.",
|
||||
price = 60,
|
||||
category = "Weapons",
|
||||
duration = 15,
|
||||
class = "tacrp_mg4",
|
||||
color = Color(255, 140, 0)
|
||||
},
|
||||
["tacrp_ex_hecate"] = {
|
||||
name = "Оружие: MSR",
|
||||
desc = "Снайперская винтовка MSR. Доступ на 13 дней. Оружие выдается через инвентарь.",
|
||||
price = 60,
|
||||
category = "Weapons",
|
||||
duration = 13,
|
||||
class = "tacrp_ex_hecate",
|
||||
color = Color(138, 43, 226)
|
||||
},
|
||||
["tacrp_ak_ak12"] = {
|
||||
name = "Оружие: AK-12",
|
||||
desc = "Современная штурмовая винтовка АК-12. Доступ на 5 дней. Оружие выдается через инвентарь.",
|
||||
price = 45,
|
||||
category = "Weapons",
|
||||
duration = 5,
|
||||
class = "tacrp_ak_ak12",
|
||||
color = Color(255, 215, 0)
|
||||
},
|
||||
["tacrp_sd_aac_hb"] = {
|
||||
name = "Оружие: LVO-AC",
|
||||
desc = "Превосходная штурмовая винтовка TFA LVO-AC. Доступ на 15 дней. Оружие выдается через инвентарь.",
|
||||
price = 50,
|
||||
category = "Weapons",
|
||||
duration = 15,
|
||||
class = "tacrp_sd_aac_hb",
|
||||
color = Color(255, 165, 0)
|
||||
}
|
||||
}
|
||||
|
||||
-- Point utility functions
|
||||
local playerMeta = debug.getregistry().Player
|
||||
|
||||
function playerMeta:GetAdminPoints()
|
||||
return self:GetNetVar("adminPoints", 0)
|
||||
end
|
||||
|
||||
function playerMeta:GetAdminInventory()
|
||||
return self:GetNetVar("adminInventory", {})
|
||||
end
|
||||
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
184
garrysmod/gamemodes/militaryrp/plugins/admin_magaz/sv_plugin.lua
Normal file
184
garrysmod/gamemodes/militaryrp/plugins/admin_magaz/sv_plugin.lua
Normal file
@@ -0,0 +1,184 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
util.AddNetworkString("ixAdminShopOpen")
|
||||
util.AddNetworkString("ixAdminShopBuy")
|
||||
util.AddNetworkString("ixAdminShopRetrieve")
|
||||
util.AddNetworkString("ixAdminShopAdminSync")
|
||||
util.AddNetworkString("ixAdminShopAdminAction")
|
||||
|
||||
function PLUGIN:Initialize()
|
||||
self.shopData = ix.data.Get("adminShop", {})
|
||||
end
|
||||
|
||||
function PLUGIN:SaveShopData()
|
||||
ix.data.Set("adminShop", self.shopData)
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerLoaded(client)
|
||||
local steamID = client:SteamID()
|
||||
self.shopData[steamID] = self.shopData[steamID] or {points = 0, inventory = {}}
|
||||
|
||||
client:SetNetVar("adminPoints", self.shopData[steamID].points)
|
||||
client:SetNetVar("adminInventory", self.shopData[steamID].inventory)
|
||||
end
|
||||
|
||||
local playerMeta = debug.getregistry().Player
|
||||
|
||||
function playerMeta:SetAdminPoints(amount)
|
||||
amount = math.max(0, amount)
|
||||
self:SetNetVar("adminPoints", amount)
|
||||
|
||||
local steamID = self:SteamID()
|
||||
PLUGIN.shopData[steamID] = PLUGIN.shopData[steamID] or {points = 0, inventory = {}}
|
||||
PLUGIN.shopData[steamID].points = amount
|
||||
PLUGIN:SaveShopData()
|
||||
end
|
||||
|
||||
function playerMeta:GiveAdminPoints(amount)
|
||||
self:SetAdminPoints(self:GetAdminPoints() + amount)
|
||||
end
|
||||
|
||||
function playerMeta:TakeAdminPoints(amount)
|
||||
self:SetAdminPoints(self:GetAdminPoints() - amount)
|
||||
end
|
||||
|
||||
function playerMeta:SetAdminInventory(data)
|
||||
self:SetNetVar("adminInventory", data)
|
||||
|
||||
local steamID = self:SteamID()
|
||||
PLUGIN.shopData[steamID] = PLUGIN.shopData[steamID] or {points = 0, inventory = {}}
|
||||
PLUGIN.shopData[steamID].inventory = data
|
||||
PLUGIN:SaveShopData()
|
||||
end
|
||||
|
||||
ix.command.Add("AdminShop", {
|
||||
description = "Открыть магазин администратора.",
|
||||
OnRun = function(self, client)
|
||||
if (!client:IsAdmin() and !client:IsSuperAdmin() and !IsAdminRank(client:GetUserGroup())) then
|
||||
client:Notify("Эта команда доступна только администраторам.")
|
||||
return
|
||||
end
|
||||
net.Start("ixAdminShopOpen")
|
||||
net.Send(client)
|
||||
end
|
||||
})
|
||||
|
||||
net.Receive("ixAdminShopBuy", function(len, client)
|
||||
local itemID = net.ReadString()
|
||||
local item = PLUGIN.Items[itemID]
|
||||
|
||||
if (!item) then return end
|
||||
|
||||
local points = client:GetAdminPoints()
|
||||
if (points < item.price) then
|
||||
client:Notify("У вас недостаточно очков!")
|
||||
return
|
||||
end
|
||||
|
||||
if (itemID == "remove_warn") then
|
||||
local warnPlugin = ix.plugin.Get("warn")
|
||||
if (warnPlugin) then
|
||||
local steamID = client:SteamID()
|
||||
local warns = ix.data.Get("warns", {})
|
||||
local targetData = warns[steamID]
|
||||
|
||||
if (targetData and targetData.count > 0) then
|
||||
targetData.count = targetData.count - 1
|
||||
table.insert(targetData.history, {
|
||||
admin = "Admin Shop",
|
||||
adminSteamID = "STEAM_0:0:0",
|
||||
reason = "Покупка в магазине",
|
||||
time = os.time(),
|
||||
type = "remove"
|
||||
})
|
||||
ix.data.Set("warns", warns)
|
||||
warnPlugin.warns = warns
|
||||
client:TakeAdminPoints(item.price)
|
||||
client:Notify("Вы успешно сняли выговор!")
|
||||
else
|
||||
client:Notify("У вас нет активных выговоров для снятия!")
|
||||
end
|
||||
else
|
||||
client:Notify("Система выговоров не найдена!")
|
||||
end
|
||||
else
|
||||
-- Immediate delivery if class exists
|
||||
if (item.class) then
|
||||
client:GiveItem(item.class)
|
||||
client:TakeAdminPoints(item.price)
|
||||
client:Notify("Вы купили " .. item.name .. ". Предмет выдан!")
|
||||
else
|
||||
-- Fallback to inventory just in case (hidden)
|
||||
local inv = client:GetAdminInventory()
|
||||
local expireTime = 0
|
||||
if (item.duration) then
|
||||
expireTime = os.time() + (item.duration * 24 * 60 * 60)
|
||||
end
|
||||
|
||||
table.insert(inv, {
|
||||
id = itemID,
|
||||
buyTime = os.time(),
|
||||
expireTime = expireTime,
|
||||
name = item.name,
|
||||
class = item.class
|
||||
})
|
||||
|
||||
client:SetAdminInventory(inv)
|
||||
client:TakeAdminPoints(item.price)
|
||||
client:Notify("Вы купили " .. item.name .. ". Предмет добавлен в инвентарь (скрыто).")
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixAdminShopRetrieve", function(len, client)
|
||||
local index = net.ReadUInt(16)
|
||||
local inv = client:GetAdminInventory()
|
||||
local itemData = inv[index]
|
||||
|
||||
if (!itemData) then return end
|
||||
|
||||
if (itemData.expireTime > 0 and os.time() > itemData.expireTime) then
|
||||
client:Notify("Срок действия предмета истек!")
|
||||
table.remove(inv, index)
|
||||
client:SetAdminInventory(inv)
|
||||
return
|
||||
end
|
||||
|
||||
if (itemData.class) then
|
||||
client:GiveItem(itemData.class)
|
||||
client:Notify("Вы получили: " .. itemData.name)
|
||||
end
|
||||
end)
|
||||
|
||||
local adminRanks = {
|
||||
["super admin"] = true,
|
||||
["superadmin"] = true,
|
||||
["projectteam"] = true,
|
||||
["teh.admin"] = true,
|
||||
["curator"] = true,
|
||||
["sudo-curator"] = true,
|
||||
["asist-sudo"] = true,
|
||||
["admin"] = true,
|
||||
["st.admin"] = true,
|
||||
["ivent"] = true,
|
||||
["st.event"] = true,
|
||||
["event"] = true,
|
||||
["disp"] = true,
|
||||
["assistant"] = true,
|
||||
["prem"] = true,
|
||||
["dsmoder"] = true
|
||||
}
|
||||
|
||||
local function IsAdminRank(rank)
|
||||
if not rank or rank == "user" then return false end
|
||||
|
||||
local lowerRank = string.lower(rank)
|
||||
|
||||
-- Check exact match in the provided list
|
||||
if adminRanks[lowerRank] then return true end
|
||||
|
||||
-- Keep generic string searches just in case
|
||||
if lowerRank == "founder" or lowerRank == "owner" or lowerRank == "manager" then return true end
|
||||
|
||||
return false
|
||||
end
|
||||
194
garrysmod/gamemodes/militaryrp/plugins/adminmode/cl_plugin.lua
Normal file
194
garrysmod/gamemodes/militaryrp/plugins/adminmode/cl_plugin.lua
Normal file
@@ -0,0 +1,194 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
function PLUGIN:HUDPaint()
|
||||
local client = LocalPlayer()
|
||||
if not IsValid(client) then return end
|
||||
|
||||
if not client:IsAdminMode() then return end
|
||||
|
||||
local scrW, scrH = ScrW(), ScrH()
|
||||
local w, h = 220, 36
|
||||
local x, y = scrW / 2 - w / 2, 20
|
||||
|
||||
surface.SetDrawColor(13, 13, 13, 220)
|
||||
surface.DrawRect(x, y, w, h)
|
||||
|
||||
surface.SetDrawColor(200, 80, 80, 255)
|
||||
surface.DrawOutlinedRect(x, y, w, h, 2)
|
||||
|
||||
draw.SimpleText("ADMIN MODE", "ixMenuButtonFont", scrW / 2, y + h / 2,
|
||||
Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
surface.CreateFont("AdminESPFontSmall", {
|
||||
font = "Arial",
|
||||
size = 12,
|
||||
weight = 500
|
||||
})
|
||||
|
||||
CLIENT_ADMIN_GROUPS = CLIENT_ADMIN_GROUPS or {}
|
||||
|
||||
local function Draw3DBox(ent, color)
|
||||
local mins, maxs = ent:OBBMins(), ent:OBBMaxs()
|
||||
local corners = {
|
||||
Vector(mins.x, mins.y, mins.z),
|
||||
Vector(mins.x, maxs.y, mins.z),
|
||||
Vector(maxs.x, maxs.y, mins.z),
|
||||
Vector(maxs.x, mins.y, mins.z),
|
||||
|
||||
Vector(mins.x, mins.y, maxs.z),
|
||||
Vector(mins.x, maxs.y, maxs.z),
|
||||
Vector(maxs.x, maxs.y, maxs.z),
|
||||
Vector(maxs.x, mins.y, maxs.z),
|
||||
}
|
||||
|
||||
local lines = {
|
||||
{1,2},{2,3},{3,4},{4,1},
|
||||
{5,6},{6,7},{7,8},{8,5},
|
||||
{1,5},{2,6},{3,7},{4,8}
|
||||
}
|
||||
|
||||
cam.Start3D()
|
||||
render.SetColorMaterial()
|
||||
for _, l in ipairs(lines) do
|
||||
render.DrawLine(
|
||||
ent:LocalToWorld(corners[l[1]]),
|
||||
ent:LocalToWorld(corners[l[2]]),
|
||||
color, true
|
||||
)
|
||||
end
|
||||
cam.End3D()
|
||||
end
|
||||
|
||||
local function CanSeeESP()
|
||||
local client = LocalPlayer()
|
||||
if not IsValid(client) then return false end
|
||||
if not client:IsAdminMode() then return false end
|
||||
|
||||
local group = client:GetUserGroup()
|
||||
if not CLIENT_ADMIN_GROUPS[group] then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
hook.Add("HUDPaint", "AdminMode_ESP", function()
|
||||
if not CanSeeESP() then return end
|
||||
|
||||
local client = LocalPlayer()
|
||||
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if ply ~= client and ply:Alive() then
|
||||
|
||||
local distance = client:GetPos():Distance(ply:GetPos())
|
||||
|
||||
Draw3DBox(ply, Color(255, 60, 60))
|
||||
|
||||
local char = ply:GetCharacter()
|
||||
local top = (ply:GetPos() + Vector(0,0,85)):ToScreen()
|
||||
local bottom = (ply:GetPos() + Vector(0,0,5)):ToScreen()
|
||||
|
||||
local faction = "Неизвестно"
|
||||
local podr = "Неизвестно"
|
||||
local spec = "Неизвестно"
|
||||
local rank = "Неизвестно"
|
||||
|
||||
local samGroup = ply:GetUserGroup()
|
||||
local samName = samGroup
|
||||
|
||||
if sam and sam.ranks and sam.ranks[samGroup] and sam.ranks[samGroup].Name then
|
||||
samName = sam.ranks[samGroup].Name
|
||||
end
|
||||
|
||||
if char then
|
||||
local factionID = char:GetFaction()
|
||||
local factionTable = ix.faction.indices[factionID]
|
||||
|
||||
if factionTable and factionTable.name then
|
||||
faction = factionTable.name
|
||||
end
|
||||
|
||||
if factionTable and factionTable.Podr and char.GetPodr then
|
||||
local id = char:GetPodr()
|
||||
if factionTable.Podr[id] and factionTable.Podr[id].name then
|
||||
podr = factionTable.Podr[id].name
|
||||
end
|
||||
end
|
||||
|
||||
if factionTable and factionTable.Spec and char.GetSpec then
|
||||
local id = char:GetSpec()
|
||||
if factionTable.Spec[id] and factionTable.Spec[id].name then
|
||||
spec = factionTable.Spec[id].name
|
||||
end
|
||||
end
|
||||
|
||||
if factionTable and factionTable.Ranks and char.GetRank then
|
||||
local id = char:GetRank()
|
||||
if factionTable.Ranks[id] and factionTable.Ranks[id][1] then
|
||||
rank = factionTable.Ranks[id][1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local steamid = ply:SteamID()
|
||||
local hp = ply:Health()
|
||||
local armor = ply:Armor()
|
||||
local wep = IsValid(ply:GetActiveWeapon()) and ply:GetActiveWeapon():GetClass() or "—"
|
||||
|
||||
local topLines = {}
|
||||
|
||||
if distance > 500 then
|
||||
-- Издалека: только ник
|
||||
topLines = { ply:Name() }
|
||||
elseif distance > 200 then
|
||||
-- Средняя дальность: ник + фракция + SteamID
|
||||
topLines = {
|
||||
ply:Name(),
|
||||
"" .. faction,
|
||||
"" .. steamid
|
||||
}
|
||||
else
|
||||
-- Близко: вся информация
|
||||
topLines = {
|
||||
ply:Name(),
|
||||
"" .. faction,
|
||||
"" .. podr,
|
||||
"" .. spec,
|
||||
"" .. rank,
|
||||
"" .. samName,
|
||||
"" .. steamid
|
||||
}
|
||||
end
|
||||
|
||||
local y = top.y
|
||||
for _, text in ipairs(topLines) do
|
||||
surface.SetDrawColor(0, 0, 0, 160)
|
||||
surface.DrawRect(top.x - 50, y - 5, 100, 12)
|
||||
|
||||
draw.SimpleText(text, "AdminESPFontSmall", top.x, y,
|
||||
Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
y = y + 15
|
||||
end
|
||||
|
||||
-- Нижняя информация всегда показывается близко
|
||||
if distance <= 200 then
|
||||
local bottomLines = {
|
||||
"HP: " .. hp,
|
||||
"Armor: " .. armor,
|
||||
"Weapon: " .. wep
|
||||
}
|
||||
|
||||
local y2 = bottom.y
|
||||
for _, text in ipairs(bottomLines) do
|
||||
surface.SetDrawColor(0, 0, 0, 160)
|
||||
surface.DrawRect(bottom.x - 40, y2 - 5, 80, 10)
|
||||
|
||||
draw.SimpleText(text, "AdminESPFontSmall", bottom.x, y2,
|
||||
Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
y2 = y2 + 12
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
386
garrysmod/gamemodes/militaryrp/plugins/adminmode/sh_plugin.lua
Normal file
386
garrysmod/gamemodes/militaryrp/plugins/adminmode/sh_plugin.lua
Normal file
@@ -0,0 +1,386 @@
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
|
||||
local PLUGIN = PLUGIN
|
||||
PLUGIN.name = "Admin Mode"
|
||||
PLUGIN.author = "Scripty"
|
||||
PLUGIN.description = "Перекид игрока по команде в админ фракцию"
|
||||
|
||||
local accessAdminGroups = {
|
||||
["superadmin"] = true,
|
||||
["super admin"] = true,
|
||||
["projectteam"] = true,
|
||||
["teh.admin"] = true,
|
||||
["curator"] = true,
|
||||
["sudo-curator"] = true,
|
||||
["asist-sudo"] = true,
|
||||
["admin"] = true,
|
||||
["st.admin"] = true,
|
||||
["ivent"] = true,
|
||||
["st.event"] = true,
|
||||
["event"] = true,
|
||||
["disp"] = true,
|
||||
["assistant"] = true,
|
||||
["specadmin"] = true
|
||||
}
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
function playerMeta:IsAdminMode()
|
||||
return self:GetNetVar("AdminMode", false)
|
||||
end
|
||||
|
||||
local function CanUseAdminMode(ply)
|
||||
return accessAdminGroups[ply:GetUserGroup()] == true
|
||||
end
|
||||
|
||||
local function CanUseNoclip(ply)
|
||||
if ply:IsAdminMode() then return true end
|
||||
|
||||
if ply:GetUserGroup() == "superadmin" then return true end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function GetWeaponsList(ply)
|
||||
local list = {}
|
||||
for _, wep in ipairs(ply:GetWeapons()) do
|
||||
if IsValid(wep) then
|
||||
list[#list + 1] = wep:GetClass()
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("AdminMode_Groups")
|
||||
|
||||
hook.Add("PlayerInitialSpawn", "AdminMode_SendGroups", function(ply)
|
||||
net.Start("AdminMode_Groups")
|
||||
net.WriteTable(accessAdminGroups)
|
||||
net.Send(ply)
|
||||
end)
|
||||
|
||||
function PLUGIN:PlayerDisconnected(ply)
|
||||
if ply:IsAdminMode() then
|
||||
local char = ply:GetCharacter()
|
||||
if char then
|
||||
local oldFaction = char:GetData("AdminMode_OldFaction")
|
||||
if oldFaction then char:SetFaction(oldFaction) end
|
||||
|
||||
local oldPodr = char:GetData("AdminMode_OldPodr")
|
||||
if oldPodr then char:SetPodr(oldPodr) end
|
||||
|
||||
local oldSpec = char:GetData("AdminMode_OldSpec")
|
||||
if oldSpec then char:SetSpec(oldSpec) end
|
||||
|
||||
local oldRank = char:GetData("AdminMode_OldRank")
|
||||
if oldRank then char:SetRank(oldRank) end
|
||||
end
|
||||
ply:SetNetVar("HideFromTab", false)
|
||||
ply:SetNetVar("AdminMode", false)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerLoadedCharacter(ply, char)
|
||||
local squads = ix.plugin.list["squads"]
|
||||
|
||||
if ply:IsAdminMode() and squads then
|
||||
local squad, squadID = squads:GetPlayerSquad(ply)
|
||||
if squad then
|
||||
if squad.leader == ply:SteamID() then
|
||||
squads:DisbandSquad(squadID)
|
||||
else
|
||||
squads:LeaveSquad(ply)
|
||||
end
|
||||
net.Start("ixSquadSync")
|
||||
net.WriteString("{}")
|
||||
net.Send(ply)
|
||||
end
|
||||
end
|
||||
|
||||
if char:GetFaction() == FACTION_ADMIN and not ply:IsAdminMode() then
|
||||
|
||||
local oldFaction = char:GetData("AdminMode_OldFaction")
|
||||
local oldPodr = char:GetData("AdminMode_OldPodr")
|
||||
local oldSpec = char:GetData("AdminMode_OldSpec")
|
||||
local oldRank = char:GetData("AdminMode_OldRank")
|
||||
|
||||
if oldFaction then char:SetFaction(oldFaction) end
|
||||
if oldPodr then char:SetPodr(oldPodr) end
|
||||
if oldSpec then char:SetSpec(oldSpec) end
|
||||
if oldRank then char:SetRank(oldRank) end
|
||||
|
||||
char:SetData("AdminMode_OldFaction", nil)
|
||||
char:SetData("AdminMode_OldPodr", nil)
|
||||
char:SetData("AdminMode_OldSpec", nil)
|
||||
char:SetData("AdminMode_OldRank", nil)
|
||||
char:SetData("AdminMode_OldModel", nil)
|
||||
char:SetData("AdminMode_OldWeapons", nil)
|
||||
|
||||
ply:SetNetVar("AdminMode", false)
|
||||
ply:SetNetVar("HideFromTab", false)
|
||||
|
||||
local radio = ix.plugin.list["radio"]
|
||||
if radio then
|
||||
radio:SyncRadioState(ply, radio.defaultFrequency, false, false)
|
||||
end
|
||||
elseif ply:IsAdminMode() and char:GetFaction() ~= FACTION_ADMIN then
|
||||
self:ToggleAdminMode(ply)
|
||||
end
|
||||
|
||||
if ply:IsAdminMode() then
|
||||
local radio = ix.plugin.list["radio"]
|
||||
if radio then
|
||||
radio:SyncRadioState(ply, radio.defaultFrequency, false, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:OnCharacterSave(char)
|
||||
local ply = char:GetPlayer()
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
if ply:IsAdminMode() then
|
||||
local oldFaction = char:GetData("AdminMode_OldFaction")
|
||||
if oldFaction then char:SetFaction(oldFaction) end
|
||||
|
||||
local oldPodr = char:GetData("AdminMode_OldPodr")
|
||||
if oldPodr then char:SetPodr(oldPodr) end
|
||||
|
||||
local oldSpec = char:GetData("AdminMode_OldSpec")
|
||||
if oldSpec then char:SetSpec(oldSpec) end
|
||||
|
||||
local oldRank = char:GetData("AdminMode_OldRank")
|
||||
if oldRank then char:SetRank(oldRank) end
|
||||
|
||||
ply:SetNetVar("AdminMode", false)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:ToggleAdminMode(ply)
|
||||
if not IsValid(ply) then return end
|
||||
if not CanUseAdminMode(ply) then
|
||||
ply:ChatPrint("[!] У вас нет доступа к админ-моду.")
|
||||
return
|
||||
end
|
||||
|
||||
local char = ply:GetCharacter()
|
||||
if not char then
|
||||
ply:ChatPrint("[!] У вас нет активного персонажа.")
|
||||
return
|
||||
end
|
||||
|
||||
local enable = not ply:IsAdminMode()
|
||||
ply:SetNetVar("AdminMode", enable)
|
||||
|
||||
if enable then
|
||||
local squads = ix.plugin.list["squads"]
|
||||
if squads then
|
||||
local squad, squadID = squads:GetPlayerSquad(ply)
|
||||
if squad then
|
||||
if squad.leader == ply:SteamID() then
|
||||
squads:DisbandSquad(squadID)
|
||||
else
|
||||
squads:LeaveSquad(ply)
|
||||
end
|
||||
end
|
||||
end
|
||||
char:SetData("AdminMode_OldFaction", char:GetFaction())
|
||||
char:SetData("AdminMode_OldPodr", char:GetPodr())
|
||||
char:SetData("AdminMode_OldSpec", char:GetSpec())
|
||||
char:SetData("AdminMode_OldRank", char:GetRank())
|
||||
|
||||
char:SetData("AdminMode_OldModel", ply:GetModel())
|
||||
char:SetData("AdminMode_OldWeapons", GetWeaponsList(ply))
|
||||
|
||||
ply:SetModel("models/ft/ft_admin.mdl")
|
||||
|
||||
if FACTION_ADMIN then
|
||||
char:SetFaction(FACTION_ADMIN)
|
||||
end
|
||||
|
||||
ply:GodEnable()
|
||||
ply:SetNoDraw(true)
|
||||
ply:SetMoveType(MOVETYPE_NOCLIP)
|
||||
|
||||
ply:StripWeapons()
|
||||
ply:Give("weapon_physgun")
|
||||
ply:Give("gmod_tool")
|
||||
ply:Give("ix_hands")
|
||||
ply:SelectWeapon("ix_hands")
|
||||
ply:SetNetVar("HideFromTab", true)
|
||||
|
||||
ply:ChatPrint("[!] Админ-мод включен.")
|
||||
else
|
||||
local oldFaction = char:GetData("AdminMode_OldFaction")
|
||||
local oldPodr = char:GetData("AdminMode_OldPodr")
|
||||
local oldSpec = char:GetData("AdminMode_OldSpec")
|
||||
local oldRank = char:GetData("AdminMode_OldRank")
|
||||
|
||||
if oldFaction and char:GetFaction() == FACTION_ADMIN then char:SetFaction(oldFaction) end
|
||||
if oldPodr then char:SetPodr(oldPodr) end
|
||||
if oldSpec then char:SetSpec(oldSpec) end
|
||||
if oldRank then char:SetRank(oldRank) end
|
||||
|
||||
local oldModel = char:GetData("AdminMode_OldModel")
|
||||
if oldModel then
|
||||
ply:SetModel(oldModel)
|
||||
end
|
||||
|
||||
local oldWeapons = char:GetData("AdminMode_OldWeapons") or {}
|
||||
ply:StripWeapons()
|
||||
|
||||
for _, class in ipairs(oldWeapons) do
|
||||
ply:Give(class)
|
||||
end
|
||||
|
||||
if ply.sam_uncloak then
|
||||
ply:sam_uncloak()
|
||||
end
|
||||
|
||||
ply:SetNoDraw(false)
|
||||
ply:SetRenderMode(RENDERMODE_NORMAL)
|
||||
ply:SetColor(Color(255, 255, 255, 255))
|
||||
ply:Fire("alpha", 255, 0)
|
||||
ply:DrawWorldModel(true)
|
||||
ply:SetMaterial("")
|
||||
|
||||
ply:SetNetVar("ixNoDraw", false)
|
||||
ply:DrawShadow(true)
|
||||
ply:SetupHands()
|
||||
|
||||
if #oldWeapons == 0 then
|
||||
ply:Give("ix_hands")
|
||||
end
|
||||
|
||||
ply:GodDisable()
|
||||
ply:SetMoveType(MOVETYPE_WALK)
|
||||
|
||||
local restoredFaction = char:GetFaction()
|
||||
local class = char:GetClass() or "default"
|
||||
|
||||
local spawnPlugin = ix.plugin.list["spawns"]
|
||||
|
||||
if spawnPlugin and spawnPlugin.spawns then
|
||||
local factionTable = ix.faction.indices[restoredFaction]
|
||||
|
||||
if factionTable then
|
||||
local factionID = factionTable.uniqueID
|
||||
local factionSpawns = spawnPlugin.spawns[factionID]
|
||||
|
||||
if factionSpawns then
|
||||
local className = "default"
|
||||
|
||||
for _, v in ipairs(ix.class.list) do
|
||||
if v.index == class then
|
||||
className = v.uniqueID
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local points = factionSpawns[className] or factionSpawns["default"]
|
||||
|
||||
if points and not table.IsEmpty(points) then
|
||||
local pos = table.Random(points)
|
||||
|
||||
timer.Simple(0.1, function()
|
||||
if IsValid(ply) then
|
||||
ply:SetPos(pos)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
char:SetData("AdminMode_OldFaction", nil)
|
||||
char:SetData("AdminMode_OldPodr", nil)
|
||||
char:SetData("AdminMode_OldSpec", nil)
|
||||
char:SetData("AdminMode_OldRank", nil)
|
||||
char:SetData("AdminMode_OldModel", nil)
|
||||
char:SetData("AdminMode_OldWeapons", nil)
|
||||
ply:SetNetVar("HideFromTab", false)
|
||||
local radio = ix.plugin.list["radio"]
|
||||
if radio then
|
||||
radio:SyncRadioState(ply, radio.defaultFrequency, false, false)
|
||||
end
|
||||
ply:ChatPrint("[!] Админ-мод выключен.")
|
||||
end
|
||||
|
||||
hook.Run("PlayerAdminModeToggled", ply, enable)
|
||||
end
|
||||
|
||||
function PLUGIN:EntityTakeDamage(target, dmg)
|
||||
if IsValid(target) and target:IsPlayer() and target:IsAdminMode() then
|
||||
return true
|
||||
end
|
||||
|
||||
local attacker = dmg:GetAttacker()
|
||||
if IsValid(attacker) and attacker:IsPlayer() and attacker:IsAdminMode() then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:OnCharacterVarChanged(char, key, oldValue, newValue)
|
||||
if (key == "faction") then
|
||||
local ply = char:GetPlayer()
|
||||
|
||||
if (IsValid(ply) and ply:IsAdminMode() and newValue ~= FACTION_ADMIN) then
|
||||
self:ToggleAdminMode(ply)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerNoClip(ply, desiredState)
|
||||
if not IsValid(ply) then return false end
|
||||
|
||||
if CanUseNoclip(ply) then
|
||||
ply:SetNoDraw(desiredState)
|
||||
|
||||
if desiredState then
|
||||
ply:SetRenderMode(RENDERMODE_NONE)
|
||||
ply:SetColor(Color(255, 255, 255, 0))
|
||||
else
|
||||
ply:SetRenderMode(RENDERMODE_NORMAL)
|
||||
ply:SetColor(Color(255, 255, 255, 255))
|
||||
end
|
||||
|
||||
if desiredState then
|
||||
ply:SetNetVar("HideFromTab", true)
|
||||
ply:SetNetVar("ixNoDraw", true)
|
||||
else
|
||||
if not ply:IsAdminMode() then
|
||||
ply:SetNetVar("HideFromTab", false)
|
||||
ply:SetNetVar("ixNoDraw", false)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
ix.command.Add("admin", {
|
||||
description = "Включить/выключить админ-мод.",
|
||||
OnRun = function(self, ply)
|
||||
PLUGIN:ToggleAdminMode(ply)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("adm", {
|
||||
description = "Синоним /admin.",
|
||||
OnRun = function(self, ply)
|
||||
PLUGIN:ToggleAdminMode(ply)
|
||||
end
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
CLIENT_ADMIN_GROUPS = CLIENT_ADMIN_GROUPS or {}
|
||||
|
||||
if CLIENT then
|
||||
net.Receive("AdminMode_Groups", function()
|
||||
CLIENT_ADMIN_GROUPS = net.ReadTable()
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
local PLUGIN = PLUGIN
|
||||
@@ -0,0 +1,5 @@
|
||||
include("shared.lua")
|
||||
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
AddCSLuaFile("cl_init.lua")
|
||||
AddCSLuaFile("shared.lua")
|
||||
include("shared.lua")
|
||||
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/sw/shared/airdrop_large.mdl")
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetUseType(SIMPLE_USE)
|
||||
|
||||
local phys = self:GetPhysicsObject()
|
||||
if (IsValid(phys)) then
|
||||
phys:Wake()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:Use(activator)
|
||||
if (IsValid(activator) and activator:IsPlayer()) then
|
||||
if (self.opened) then return end
|
||||
self.opened = true
|
||||
|
||||
local weaponClass = self.ixWeapon or "tacrp_m320"
|
||||
local character = activator:GetCharacter()
|
||||
local inventory = character and character:GetInventory()
|
||||
|
||||
if (inventory and !isnumber(inventory) and ix.item.list[weaponClass]) then
|
||||
inventory:Add(weaponClass)
|
||||
ix.util.Notify("Вы получили " .. (ix.item.list[weaponClass].name or weaponClass) .. " из аирдропа.", activator)
|
||||
else
|
||||
activator:Give(weaponClass)
|
||||
ix.util.Notify("Вы получили " .. (self.ixWeapon or "неизвестное оружие") .. " из аирдропа.", activator)
|
||||
end
|
||||
|
||||
if (IsValid(self.ixSmoke)) then
|
||||
self.ixSmoke:Remove()
|
||||
end
|
||||
|
||||
local effect = EffectData()
|
||||
effect:SetOrigin(self:GetPos())
|
||||
effect:SetScale(1)
|
||||
util.Effect("Explosion", effect)
|
||||
|
||||
self:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:OnTakeDamage(damage)
|
||||
if (damage:GetDamage() > 50) then
|
||||
self:Use(damage:GetAttacker())
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Airdrop"
|
||||
ENT.Category = "Helix"
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminOnly = true
|
||||
92
garrysmod/gamemodes/militaryrp/plugins/airdrop/sh_plugin.lua
Normal file
92
garrysmod/gamemodes/militaryrp/plugins/airdrop/sh_plugin.lua
Normal file
@@ -0,0 +1,92 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
PLUGIN.name = "Airdrop"
|
||||
PLUGIN.author = "Scripty"
|
||||
PLUGIN.description = "Airdrop plugin."
|
||||
|
||||
ix.config.Add("airdropPlaneModel", "models/il76/il_76_fly.mdl", "Модель самолета для аирдропа.", nil, {
|
||||
category = "Airdrop"
|
||||
})
|
||||
|
||||
ix.config.Add("airstrikePlaneModel", "models/gunkov2056/su25.mdl", "Модель самолета для авиаудара.", nil, {
|
||||
category = "Airdrop"
|
||||
})
|
||||
|
||||
ix.config.Add("airdropMinInterval", 3600, "Минимальный интервал между аирдропами (в секундах).", nil, {
|
||||
data = {min = 60, max = 86400},
|
||||
category = "Airdrop"
|
||||
})
|
||||
|
||||
ix.config.Add("airdropMaxInterval", 7200, "Максимальный интервал между аирдропами (в секундах).", nil, {
|
||||
data = {min = 60, max = 86400},
|
||||
category = "Airdrop"
|
||||
})
|
||||
|
||||
ix.config.Add("airdropWeapons", [[tacrp_io_degala
|
||||
tacrp_io_fiveseven
|
||||
tacrp_mr96
|
||||
tacrp_pdw
|
||||
tacrp_superv
|
||||
tacrp_sd_aac_hb
|
||||
tacrp_ak_ak12
|
||||
tacrp_ak_an94
|
||||
tacrp_sg551
|
||||
tacrp_io_xm8car
|
||||
tacrp_hk417
|
||||
tacrp_io_scarh
|
||||
tacrp_mg4
|
||||
tacrp_io_xm8lmg
|
||||
tacrp_io_sg550r
|
||||
tacrp_io_sl8
|
||||
tacrp_io_sg550
|
||||
tacrp_io_vss
|
||||
tacrp_as50
|
||||
tacrp_ex_hecate
|
||||
tacrp_civ_m320]], "Список оружия, которое может выпасть из аирдропа (каждое с новой строки).", nil, {
|
||||
category = "Airdrop"
|
||||
})
|
||||
|
||||
ix.config.Add("airstrikeMinInterval", 1800, "Минимальный интервал между авиаударами (в секундах).", nil, {
|
||||
data = {min = 60, max = 86400},
|
||||
category = "Airdrop"
|
||||
})
|
||||
|
||||
ix.config.Add("airstrikeMaxInterval", 3600, "Максимальный интервал между авиаударами (в секундах).", nil, {
|
||||
data = {min = 60, max = 86400},
|
||||
category = "Airdrop"
|
||||
})
|
||||
|
||||
ix.command.Add("AirdropForce", {
|
||||
description = "Принудительно вызвать случайный аирдроп.",
|
||||
privilege = "Manage Airdrops",
|
||||
superAdminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
PLUGIN:SpawnAirdrop()
|
||||
ix.util.Notify("Аирдроп был вызван администратором.", nil, "all")
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("AirdropGive", {
|
||||
description = "Вызвать аирдроп с конкретным оружием.",
|
||||
privilege = "Manage Airdrops",
|
||||
superAdminOnly = true,
|
||||
arguments = {
|
||||
ix.type.string
|
||||
},
|
||||
OnRun = function(self, client, weapon)
|
||||
PLUGIN:SpawnAirdrop(weapon)
|
||||
ix.util.Notify("Аирдроп с " .. weapon .. " был вызван администратором.", nil, "all")
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("AirstrikeForce", {
|
||||
description = "Принудительно вызвать авиаудар.",
|
||||
privilege = "Manage Airdrops",
|
||||
superAdminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
PLUGIN:SpawnAirstrike()
|
||||
ix.util.Notify("Авиаудар был вызван администратором.", nil, "all")
|
||||
end
|
||||
})
|
||||
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
329
garrysmod/gamemodes/militaryrp/plugins/airdrop/sv_plugin.lua
Normal file
329
garrysmod/gamemodes/militaryrp/plugins/airdrop/sv_plugin.lua
Normal file
@@ -0,0 +1,329 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
-- Координаты баз для запрета авиаударов
|
||||
local BASE_RF = {
|
||||
min = Vector(-12686, 4350, -10000),
|
||||
max = Vector(-8714, 12126, 10000)
|
||||
}
|
||||
local BASE_UK = {
|
||||
min = Vector(9778, 2972, -10000),
|
||||
max = Vector(13576, 10852, 10000)
|
||||
}
|
||||
|
||||
local function IsInBase(pos)
|
||||
if (pos:WithinAABox(BASE_RF.min, BASE_RF.max)) then return true end
|
||||
if (pos:WithinAABox(BASE_UK.min, BASE_UK.max)) then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
function PLUGIN:SpawnAirdrop(weapon)
|
||||
local center = Vector(0, 0, 0)
|
||||
local players = player.GetAll()
|
||||
if (#players > 0) then
|
||||
local targetPlayer = players[math.random(#players)]
|
||||
center = targetPlayer:GetPos()
|
||||
end
|
||||
|
||||
local angle = math.random(0, 360)
|
||||
local radian = math.rad(angle)
|
||||
local altitude = math.random(2000, 2500)
|
||||
local startPos = center + Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0)
|
||||
startPos.z = altitude
|
||||
local endPos = center - Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0)
|
||||
endPos.z = altitude
|
||||
|
||||
util.PrecacheModel(ix.config.Get("airdropPlaneModel", "models/gunkov2056/su25.mdl"))
|
||||
local plane = ents.Create("prop_dynamic")
|
||||
plane:SetModel(ix.config.Get("airdropPlaneModel", "models/gunkov2056/su25.mdl"))
|
||||
plane:SetPos(startPos)
|
||||
local angles = (endPos - startPos):Angle()
|
||||
plane:SetAngles(angles)
|
||||
plane:SetMoveType(MOVETYPE_FLY)
|
||||
plane:Spawn()
|
||||
plane:Activate()
|
||||
|
||||
print("[Airdrop] Спавн самолета: " .. plane:GetModel() .. " на " .. tostring(startPos))
|
||||
|
||||
local speed = 2500
|
||||
local distance = startPos:Distance(endPos)
|
||||
local duration = distance / speed
|
||||
local dropFrac = math.Rand(0.45, 0.55)
|
||||
local dropped = false
|
||||
local startTime = CurTime()
|
||||
|
||||
plane:EmitSound("su25/su25.wav", 150, 100, 1, CHAN_AUTO)
|
||||
|
||||
timer.Create("AirdropPlaneMove_" .. plane:EntIndex(), 0.05, 0, function()
|
||||
if (!IsValid(plane)) then return end
|
||||
local elapsed = CurTime() - startTime
|
||||
local frac = elapsed / duration
|
||||
if (frac >= 1) then
|
||||
plane:StopSound("su25/su25.wav")
|
||||
plane:Remove()
|
||||
timer.Remove("AirdropPlaneMove_" .. plane:EntIndex())
|
||||
return
|
||||
end
|
||||
plane:SetPos(LerpVector(frac, startPos, endPos))
|
||||
if (!dropped and frac >= dropFrac) then
|
||||
dropped = true
|
||||
self:DropCrate(plane:GetPos(), weapon)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function PLUGIN:SpawnAirstrike()
|
||||
ix.util.Notify("Внимание! Обнаружена воздушная угроза. Авиаудар через 30 секунд!", nil, "all")
|
||||
|
||||
timer.Simple(30, function()
|
||||
local targetPos = Vector(0, 0, 0)
|
||||
local players = player.GetAll()
|
||||
local attempts = 0
|
||||
|
||||
-- Ищем валидную точку для удара (не в базе)
|
||||
repeat
|
||||
if (#players > 0) then
|
||||
targetPos = players[math.random(#players)]:GetPos()
|
||||
else
|
||||
targetPos = Vector(math.random(-10000, 10000), math.random(-10000, 10000), 0)
|
||||
end
|
||||
attempts = attempts + 1
|
||||
until (!IsInBase(targetPos) or attempts > 10)
|
||||
|
||||
if (IsInBase(targetPos)) then return end -- Отмена если не нашли безопасную точку
|
||||
|
||||
local angle = math.random(0, 360)
|
||||
local radian = math.rad(angle)
|
||||
local altitude = math.random(2000, 2500)
|
||||
local startPos = targetPos + Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0)
|
||||
startPos.z = altitude
|
||||
local endPos = targetPos - Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0)
|
||||
endPos.z = altitude
|
||||
|
||||
util.PrecacheModel(ix.config.Get("airstrikePlaneModel", "models/gunkov2056/su25.mdl"))
|
||||
local plane = ents.Create("prop_dynamic")
|
||||
plane:SetModel(ix.config.Get("airstrikePlaneModel", "models/gunkov2056/su25.mdl"))
|
||||
plane:SetPos(startPos)
|
||||
plane:SetAngles((endPos - startPos):Angle())
|
||||
plane:SetMoveType(MOVETYPE_FLY)
|
||||
plane:Spawn()
|
||||
plane:Activate()
|
||||
|
||||
print("[Airstrike] Спавн самолета: " .. plane:GetModel() .. " на " .. tostring(startPos))
|
||||
|
||||
local speed = 3000
|
||||
local duration = startPos:Distance(endPos) / speed
|
||||
local dropped = false
|
||||
local startTime = CurTime()
|
||||
|
||||
plane:EmitSound("su25/su25.wav", 150, 100, 1, CHAN_AUTO)
|
||||
|
||||
timer.Create("AirstrikePlaneMove_" .. plane:EntIndex(), 0.05, 0, function()
|
||||
if (!IsValid(plane)) then return end
|
||||
local elapsed = CurTime() - startTime
|
||||
local frac = elapsed / duration
|
||||
if (frac >= 1) then
|
||||
plane:StopSound("su25/su25.wav")
|
||||
plane:Remove()
|
||||
timer.Remove("AirstrikePlaneMove_" .. plane:EntIndex())
|
||||
return
|
||||
end
|
||||
plane:SetPos(LerpVector(frac, startPos, endPos))
|
||||
if (!dropped and frac >= 0.5) then
|
||||
dropped = true
|
||||
self:DropBomb(plane:GetPos(), plane:GetForward() * speed)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function PLUGIN:DropBomb(pos, velocity)
|
||||
local bomb = ents.Create("sw_bomb_fab250m62_v3")
|
||||
if (!IsValid(bomb)) then
|
||||
-- Если энтити нет, создадим обычный взрыв для теста
|
||||
local exp = ents.Create("env_explosion")
|
||||
exp:SetPos(pos)
|
||||
exp:SetKeyValue("iMagnitude", "300")
|
||||
exp:Spawn()
|
||||
exp:Fire("Explode", 0, 0)
|
||||
return
|
||||
end
|
||||
|
||||
bomb:SetPos(pos)
|
||||
bomb:SetAngles(velocity:Angle())
|
||||
bomb:Spawn()
|
||||
bomb:Activate()
|
||||
|
||||
local phys = bomb:GetPhysicsObject()
|
||||
if (IsValid(phys)) then
|
||||
phys:SetVelocity(velocity)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:DropCrate(pos, weapon)
|
||||
if (!weapon) then
|
||||
local weaponsConfig = ix.config.Get("airdropWeapons", "")
|
||||
local weapons = {}
|
||||
|
||||
if (type(weaponsConfig) == "string" and weaponsConfig:Trim() != "") then
|
||||
for _, v in pairs(string.Split(weaponsConfig, "\n")) do
|
||||
local s = v:Trim()
|
||||
if (s != "") then
|
||||
table.insert(weapons, s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Если конфиг пустой или старый, берем список по умолчанию
|
||||
if (#weapons == 0) then
|
||||
weapons = {
|
||||
"tacrp_io_degala",
|
||||
"tacrp_io_fiveseven",
|
||||
"tacrp_mr96",
|
||||
"tacrp_pdw",
|
||||
"tacrp_superv",
|
||||
"tacrp_sd_aac_hb",
|
||||
"tacrp_ak_ak12",
|
||||
"tacrp_ak_an94",
|
||||
"tacrp_sg551",
|
||||
"tacrp_io_xm8car",
|
||||
"tacrp_hk417",
|
||||
"tacrp_io_scarh",
|
||||
"tacrp_mg4",
|
||||
"tacrp_io_xm8lmg",
|
||||
"tacrp_io_sg550r",
|
||||
"tacrp_io_sl8",
|
||||
"tacrp_io_sg550",
|
||||
"tacrp_io_vss",
|
||||
"tacrp_as50",
|
||||
"tacrp_ex_hecate",
|
||||
"tacrp_civ_m320"
|
||||
}
|
||||
end
|
||||
|
||||
weapon = weapons[math.random(#weapons)]
|
||||
end
|
||||
|
||||
local crate = ents.Create("ix_airdrop")
|
||||
if (!IsValid(crate)) then
|
||||
print("[Airdrop] ОШИБКА: Не удалось создать энтити ix_airdrop!")
|
||||
return
|
||||
end
|
||||
crate:SetPos(pos)
|
||||
crate:SetAngles(Angle(0, math.random(0, 360), 0))
|
||||
crate:Spawn()
|
||||
crate.ixWeapon = weapon
|
||||
|
||||
-- Создаем парашют
|
||||
util.PrecacheModel("models/jessev92/bf2/parachute.mdl")
|
||||
local parachute = ents.Create("prop_dynamic")
|
||||
parachute:SetModel("models/jessev92/bf2/parachute.mdl")
|
||||
parachute:SetPos(crate:GetPos() + Vector(0, 0, 50))
|
||||
parachute:SetParent(crate)
|
||||
parachute:Spawn()
|
||||
crate.ixParachute = parachute
|
||||
|
||||
local phys = crate:GetPhysicsObject()
|
||||
if (IsValid(phys)) then
|
||||
phys:Wake()
|
||||
phys:SetVelocity(Vector(0, 0, -200)) -- Скорость падения
|
||||
phys:SetDragCoefficient(5)
|
||||
phys:SetAngleDragCoefficient(100)
|
||||
end
|
||||
|
||||
local function StartSmoke()
|
||||
if (!IsValid(crate)) then return end
|
||||
local smoke = ents.Create("env_smokestack")
|
||||
smoke:SetPos(crate:GetPos())
|
||||
smoke:SetAngles(Angle(-90, 0, 0))
|
||||
smoke:SetKeyValue("InitialState", "1")
|
||||
smoke:SetKeyValue("rendercolor", "255 100 0")
|
||||
smoke:SetKeyValue("renderamt", "255")
|
||||
smoke:SetKeyValue("SmokeMaterial", "particle/smokesprites_0001.vmt")
|
||||
smoke:SetKeyValue("BaseSpread", "20")
|
||||
smoke:SetKeyValue("SpreadSpeed", "10")
|
||||
smoke:SetKeyValue("Speed", "80")
|
||||
smoke:SetKeyValue("StartSize", "30")
|
||||
smoke:SetKeyValue("EndSize", "120")
|
||||
smoke:SetKeyValue("Rate", "40")
|
||||
smoke:SetKeyValue("JetLength", "300")
|
||||
smoke:Spawn()
|
||||
smoke:Activate()
|
||||
smoke:SetParent(crate)
|
||||
crate.ixSmoke = smoke
|
||||
ix.util.Notify("Аирдроп приземлился! Ищите оранжевый дым.", nil, "all")
|
||||
end
|
||||
|
||||
-- Принудительно держим ящик ровно во время падения
|
||||
timer.Create("AirdropUpright_" .. crate:EntIndex(), 0.1, 0, function()
|
||||
if (!IsValid(crate)) then
|
||||
timer.Remove("AirdropUpright_" .. crate:EntIndex())
|
||||
return
|
||||
end
|
||||
|
||||
local phys = crate:GetPhysicsObject()
|
||||
if (IsValid(phys) and phys:IsMotionEnabled()) then
|
||||
crate:SetAngles(Angle(0, crate:GetAngles().y, 0))
|
||||
phys:SetAngleVelocity(Vector(0, 0, 0))
|
||||
else
|
||||
timer.Remove("AirdropUpright_" .. crate:EntIndex())
|
||||
end
|
||||
end)
|
||||
|
||||
timer.Create("AirdropLand_" .. crate:EntIndex(), 0.2, 0, function()
|
||||
if (!IsValid(crate)) then
|
||||
timer.Remove("AirdropLand_" .. crate:EntIndex())
|
||||
return
|
||||
end
|
||||
local phys = crate:GetPhysicsObject()
|
||||
if (!IsValid(phys)) then return end
|
||||
|
||||
-- Если скорость мала или мы коснулись земли
|
||||
local tr = util.TraceLine({
|
||||
start = crate:GetPos() + Vector(0, 0, 10),
|
||||
endpos = crate:GetPos() - Vector(0, 0, 40),
|
||||
filter = crate
|
||||
})
|
||||
|
||||
if (tr.Hit or phys:GetVelocity():Length() < 5) then
|
||||
phys:EnableMotion(false)
|
||||
crate:SetPos(tr.HitPos)
|
||||
crate:SetAngles(Angle(0, crate:GetAngles().y, 0))
|
||||
|
||||
if (IsValid(crate.ixParachute)) then
|
||||
crate.ixParachute:Remove()
|
||||
end
|
||||
|
||||
StartSmoke()
|
||||
timer.Remove("AirdropLand_" .. crate:EntIndex())
|
||||
timer.Remove("AirdropUpright_" .. crate:EntIndex())
|
||||
end
|
||||
end)
|
||||
|
||||
timer.Simple(900, function()
|
||||
if (IsValid(crate)) then
|
||||
if (IsValid(crate.ixSmoke)) then crate.ixSmoke:Remove() end
|
||||
crate:Remove()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function PLUGIN:Initialize()
|
||||
self:SetupAirdropTimer()
|
||||
self:SetupAirstrikeTimer()
|
||||
end
|
||||
|
||||
function PLUGIN:SetupAirdropTimer()
|
||||
local delay = math.random(ix.config.Get("airdropMinInterval", 3600), ix.config.Get("airdropMaxInterval", 7200))
|
||||
timer.Create("ixAirdropTimer", delay, 1, function()
|
||||
self:SpawnAirdrop()
|
||||
self:SetupAirdropTimer()
|
||||
end)
|
||||
end
|
||||
|
||||
function PLUGIN:SetupAirstrikeTimer()
|
||||
local delay = math.random(ix.config.Get("airstrikeMinInterval", 1800), ix.config.Get("airstrikeMaxInterval", 3600))
|
||||
timer.Create("ixAirstrikeTimer", delay, 1, function()
|
||||
self:SpawnAirstrike()
|
||||
self:SetupAirstrikeTimer()
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,553 @@
|
||||
-- Цветовая схема (в стиле остальных интерфейсов)
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
local COLOR_BG_DARK = Color(3, 5, 4)
|
||||
local COLOR_BG_MEDIUM = Color(8, 12, 10)
|
||||
local COLOR_BG_LIGHT = Color(12, 18, 14)
|
||||
local COLOR_PRIMARY = Color(27, 94, 32)
|
||||
local COLOR_PRIMARY_HOVER = Color(33, 110, 38)
|
||||
local COLOR_ACCENT = Color(56, 102, 35)
|
||||
local COLOR_ACCENT_HOVER = Color(66, 120, 45)
|
||||
local COLOR_BORDER = Color(46, 125, 50, 80)
|
||||
local COLOR_TEXT_PRIMARY = Color(165, 214, 167)
|
||||
local COLOR_TEXT_SECONDARY = Color(129, 199, 132)
|
||||
local COLOR_TEXT_DIM = Color(100, 150, 105)
|
||||
|
||||
-- Хук для камеры во время анимации
|
||||
function PLUGIN:CalcView(ply, pos, angles, fov)
|
||||
local str = ply:GetNW2String('TauntAnim')
|
||||
if str != "" then
|
||||
if GetConVar("rp_taunt_firstperson"):GetBool() then
|
||||
local eye_id = ply:LookupAttachment('eyes')
|
||||
local att = eye_id > 0 and ply:GetAttachment(eye_id) or nil
|
||||
|
||||
if not att then return end
|
||||
|
||||
local ang1 = angles
|
||||
if GetConVar("rp_taunt_realistic_firstperson"):GetBool() then
|
||||
ang1 = att.Ang
|
||||
end
|
||||
local view = {
|
||||
origin = att.Pos,
|
||||
angles = ang1,
|
||||
fov = fov,
|
||||
drawviewer = true
|
||||
}
|
||||
return view
|
||||
else
|
||||
local view = {
|
||||
origin = pos - (angles:Forward() * 100),
|
||||
angles = angles,
|
||||
fov = fov,
|
||||
drawviewer = true
|
||||
}
|
||||
return view
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:ShouldDrawLocalPlayer(ply)
|
||||
local str = ply:GetNW2String('TauntAnim')
|
||||
if str != "" and not GetConVar("rp_taunt_firstperson"):GetBool() then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Подсказка на экране
|
||||
function PLUGIN:HUDPaint()
|
||||
local ply = LocalPlayer()
|
||||
local str = ply:GetNW2String('TauntAnim')
|
||||
if str != "" then
|
||||
draw.SimpleTextOutlined("Нажмите SHIFT, чтобы убрать анимацию.", "Trebuchet18", ScrW()/2, ScrH()-250, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, color_black)
|
||||
end
|
||||
end
|
||||
|
||||
-- Таблица анимаций (импортируем из существующего аддона)
|
||||
local AnimationCategories = {
|
||||
{
|
||||
name = "ЖЕСТЫ",
|
||||
anims = {
|
||||
{id = 1, name = "Помахать руками"},
|
||||
{id = 2, name = "Помахать рукой"},
|
||||
{id = 3, name = "Показать пальцем вперед"},
|
||||
{id = 4, name = "Показать пальцем назад"},
|
||||
{id = 5, name = "Поправить галстук"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "СПОРТ",
|
||||
anims = {
|
||||
{id = 6, name = "Приседания"},
|
||||
{id = 7, name = "Отжимания"},
|
||||
{id = 8, name = "Подъем корпуса"},
|
||||
{id = 9, name = "Берпи"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "ПОЗЫ",
|
||||
anims = {
|
||||
{id = 10, name = "Стоять злобно"},
|
||||
{id = 11, name = "Стоять напуганно"},
|
||||
{id = 12, name = "Стоять, сложив руки"},
|
||||
{id = 13, name = "Стоять, руки на поясе"},
|
||||
{id = 14, name = "Стоять, держась за пояс"},
|
||||
{id = 15, name = "Сидеть, рука на колене"},
|
||||
{id = 16, name = "Сидеть в позе лотоса"},
|
||||
{id = 17, name = "Сидеть в сонном состоянии"},
|
||||
{id = 18, name = "Лежать в плохом состоянии"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "ТАНЦЫ",
|
||||
anims = {
|
||||
{id = 19, name = "Танец 1"},
|
||||
{id = 20, name = "Танец 2"},
|
||||
{id = 21, name = "Танец 3"},
|
||||
{id = 22, name = "Танец 4"},
|
||||
{id = 33, name = "Танец в присядь"},
|
||||
}
|
||||
},
|
||||
{
|
||||
name = "ДЕЙСТВИЯ",
|
||||
anims = {
|
||||
{id = 23, name = "Согласие"},
|
||||
{id = 24, name = "Несогласие"},
|
||||
{id = 25, name = "Позвать с собой"},
|
||||
{id = 26, name = "Поклониться"},
|
||||
{id = 27, name = "Отдать честь"},
|
||||
{id = 28, name = "Пить кофе"},
|
||||
{id = 29, name = "Смотреть на объект"},
|
||||
{id = 30, name = "Записывать в блокнот"},
|
||||
{id = 31, name = "Спать"},
|
||||
{id = 32, name = "Воинское приветствие"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local animMenu = nil
|
||||
local animMenuOpen = false
|
||||
|
||||
-- Функция создания меню анимаций
|
||||
local function CreateAnimationMenu()
|
||||
if IsValid(animMenu) then
|
||||
animMenu:Remove()
|
||||
end
|
||||
|
||||
-- Невидимая панель на весь экран для перехвата ввода
|
||||
local basePanel = vgui.Create("DPanel")
|
||||
basePanel:SetSize(ScrW(), ScrH())
|
||||
basePanel:SetPos(0, 0)
|
||||
basePanel:MakePopup()
|
||||
basePanel.Paint = function() end -- Полностью прозрачная
|
||||
|
||||
-- Меню в правом верхнем углу
|
||||
local menuWidth = 320
|
||||
local menuHeight = 500
|
||||
local padding = 20
|
||||
|
||||
local menu = vgui.Create("DPanel", basePanel)
|
||||
menu:SetSize(menuWidth, menuHeight)
|
||||
menu:SetPos(ScrW() - menuWidth - padding, padding)
|
||||
menu.Paint = function(s, w, h)
|
||||
-- Основной фон
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
|
||||
|
||||
-- Заголовок
|
||||
draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawLine(0, 45, w, 45)
|
||||
|
||||
-- Текст заголовка
|
||||
draw.SimpleText("АНИМАЦИИ", "DermaLarge", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Декоративная линия
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(10, 43, w-20, 2)
|
||||
|
||||
-- Внешняя граница
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
|
||||
-- Подсказка внизу
|
||||
draw.SimpleText("C - Закрыть | SHIFT - Остановить анимацию", "DermaDefault", w/2, h-15, COLOR_TEXT_DIM, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
-- Кнопка закрытия
|
||||
local closeBtn = vgui.Create("DButton", menu)
|
||||
closeBtn:SetPos(menuWidth - 35, 8)
|
||||
closeBtn:SetSize(28, 28)
|
||||
closeBtn:SetText("")
|
||||
closeBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_ACCENT_HOVER or COLOR_ACCENT
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
draw.SimpleText("×", "DermaLarge", w/2, h/2-2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
closeBtn.DoClick = function()
|
||||
basePanel:Remove()
|
||||
animMenuOpen = false
|
||||
end
|
||||
|
||||
-- Скролл панель для категорий
|
||||
local scroll = vgui.Create("DScrollPanel", menu)
|
||||
scroll:SetPos(10, 55)
|
||||
scroll:SetSize(menuWidth - 20, menuHeight - 90)
|
||||
|
||||
local sbar = scroll:GetVBar()
|
||||
sbar:SetWide(6)
|
||||
sbar.Paint = function(s, w, h)
|
||||
draw.RoundedBox(3, 0, 0, w, h, COLOR_BG_MEDIUM)
|
||||
end
|
||||
sbar.btnGrip.Paint = function(s, w, h)
|
||||
draw.RoundedBox(3, 0, 0, w, h, COLOR_ACCENT)
|
||||
end
|
||||
sbar.btnUp.Paint = function() end
|
||||
sbar.btnDown.Paint = function() end
|
||||
|
||||
-- Создаем категории с анимациями
|
||||
for _, category in ipairs(AnimationCategories) do
|
||||
-- Заголовок категории
|
||||
local catHeader = vgui.Create("DPanel", scroll)
|
||||
catHeader:Dock(TOP)
|
||||
catHeader:DockMargin(0, 5, 0, 3)
|
||||
catHeader:SetTall(28)
|
||||
catHeader.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText(category.name, "DermaDefaultBold", 10, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
-- Анимации в категории
|
||||
for _, anim in ipairs(category.anims) do
|
||||
local animBtn = vgui.Create("DButton", scroll)
|
||||
animBtn:Dock(TOP)
|
||||
animBtn:DockMargin(3, 2, 3, 0)
|
||||
animBtn:SetTall(32)
|
||||
animBtn:SetText("")
|
||||
|
||||
animBtn.Paint = function(s, w, h)
|
||||
local col = COLOR_BG_MEDIUM
|
||||
if s:IsHovered() then
|
||||
col = COLOR_PRIMARY_HOVER
|
||||
-- Акцентная линия слева
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(0, 0, 3, h)
|
||||
end
|
||||
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
|
||||
-- Иконка
|
||||
draw.SimpleText("▶", "DermaDefault", 12, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Название анимации
|
||||
draw.SimpleText(anim.name, "DermaDefault", 28, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
animBtn.DoClick = function()
|
||||
RunConsoleCommand("rp_set_taunt", tostring(anim.id))
|
||||
surface.PlaySound("buttons/button15.wav")
|
||||
basePanel:Remove()
|
||||
animMenuOpen = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Обработка закрытия по нажатию вне меню
|
||||
basePanel.OnMousePressed = function(s, keyCode)
|
||||
if keyCode == MOUSE_LEFT then
|
||||
-- Проверяем, кликнули ли вне меню
|
||||
local mx, my = input.GetCursorPos()
|
||||
local x, y = menu:GetPos()
|
||||
local w, h = menu:GetSize()
|
||||
|
||||
if mx < x or mx > x + w or my < y or my > y + h then
|
||||
basePanel:Remove()
|
||||
animMenuOpen = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Меню рации в левом верхнем углу
|
||||
local radioWidth = 320
|
||||
local radioHeight = 200
|
||||
|
||||
local radioMenu = vgui.Create("DPanel", basePanel)
|
||||
radioMenu:SetSize(radioWidth, radioHeight)
|
||||
radioMenu:SetPos(padding, padding)
|
||||
radioMenu.Paint = function(s, w, h)
|
||||
-- Основной фон
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
|
||||
|
||||
-- Заголовок
|
||||
draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawLine(0, 45, w, 45)
|
||||
|
||||
-- Текст заголовка
|
||||
draw.SimpleText("ЧАСТОТА РАЦИИ", "DermaLarge", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Декоративная линия
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(10, 43, w-20, 2)
|
||||
|
||||
-- Внешняя граница
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
-- Получаем плагин рации
|
||||
local radioPlugin = ix.plugin.list["radio"]
|
||||
if not radioPlugin then
|
||||
radioMenu:SetVisible(false)
|
||||
else
|
||||
local currentFreq = radioPlugin:GetFrequency(LocalPlayer())
|
||||
|
||||
-- Текущая частота
|
||||
local freqLabel = vgui.Create("DLabel", radioMenu)
|
||||
freqLabel:SetPos(20, 60)
|
||||
freqLabel:SetSize(radioWidth - 40, 25)
|
||||
freqLabel:SetText("Текущая частота: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", currentFreq))
|
||||
freqLabel:SetFont("DermaDefault")
|
||||
freqLabel:SetTextColor(COLOR_TEXT_PRIMARY)
|
||||
|
||||
-- Слайдер частоты
|
||||
local freqSlider = vgui.Create("DNumSlider", radioMenu)
|
||||
freqSlider:SetPos(10, 90)
|
||||
freqSlider:SetSize(radioWidth - 20, 40)
|
||||
freqSlider:SetText("")
|
||||
freqSlider:SetMin(radioPlugin.minFrequency)
|
||||
freqSlider:SetMax(radioPlugin.maxFrequency)
|
||||
freqSlider:SetDecimals(radioPlugin.frequencyPrecision)
|
||||
freqSlider:SetValue(currentFreq)
|
||||
|
||||
freqSlider.Label:SetTextColor(COLOR_TEXT_SECONDARY)
|
||||
freqSlider.Label:SetFont("DermaDefault")
|
||||
freqSlider.TextArea:SetTextColor(COLOR_TEXT_PRIMARY)
|
||||
freqSlider.TextArea:SetFont("DermaDefault")
|
||||
freqSlider.TextArea:SetEditable(true)
|
||||
freqSlider.TextArea:SetNumeric(true)
|
||||
freqSlider.TextArea.Paint = function(panel, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_MEDIUM)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
panel:DrawTextEntryText(COLOR_TEXT_PRIMARY, COLOR_ACCENT, COLOR_TEXT_PRIMARY)
|
||||
end
|
||||
|
||||
-- Обработчик ввода вручную
|
||||
freqSlider.TextArea.OnEnter = function(panel)
|
||||
local val = tonumber(panel:GetValue())
|
||||
if val then
|
||||
val = math.Clamp(val, radioPlugin.minFrequency, radioPlugin.maxFrequency)
|
||||
freqSlider:SetValue(val)
|
||||
end
|
||||
end
|
||||
|
||||
freqSlider.Slider.Paint = function(panel, w, h)
|
||||
draw.RoundedBox(4, 0, h/2-2, w, 4, COLOR_BG_MEDIUM)
|
||||
local progress = (freqSlider:GetValue() - freqSlider:GetMin()) / (freqSlider:GetMax() - freqSlider:GetMin())
|
||||
draw.RoundedBox(4, 0, h/2-2, w * progress, 4, COLOR_ACCENT)
|
||||
end
|
||||
|
||||
freqSlider.Slider.Knob.Paint = function(s, w, h)
|
||||
draw.RoundedBox(w/2, 0, 0, w, h, COLOR_PRIMARY)
|
||||
if s:IsHovered() then
|
||||
draw.RoundedBox(w/2, 2, 2, w-4, h-4, COLOR_ACCENT_HOVER)
|
||||
end
|
||||
end
|
||||
|
||||
freqSlider.OnValueChanged = function(_, value)
|
||||
local format = "%0." .. radioPlugin.frequencyPrecision .. "f"
|
||||
freqLabel:SetText("Текущая частота: " .. string.format(format, value))
|
||||
end
|
||||
|
||||
-- Кнопка применения
|
||||
local btnWidth = (radioWidth - 50) / 2
|
||||
local applyBtn = vgui.Create("DButton", radioMenu)
|
||||
applyBtn:SetPos(20, 150)
|
||||
applyBtn:SetSize(btnWidth, 35)
|
||||
applyBtn:SetText("")
|
||||
applyBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_PRIMARY_HOVER or COLOR_PRIMARY
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
draw.SimpleText("ПРИМЕНИТЬ", "DermaDefaultBold", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
applyBtn.DoClick = function()
|
||||
net.Start("ixRadioSetFrequency")
|
||||
net.WriteFloat(freqSlider:GetValue())
|
||||
net.SendToServer()
|
||||
|
||||
LocalPlayer():Notify("Частота рации изменена на: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", freqSlider:GetValue()))
|
||||
end
|
||||
|
||||
-- Кнопка ручного ввода
|
||||
local manualBtn = vgui.Create("DButton", radioMenu)
|
||||
manualBtn:SetPos(30 + btnWidth, 150)
|
||||
manualBtn:SetSize(btnWidth, 35)
|
||||
manualBtn:SetText("")
|
||||
manualBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_ACCENT_HOVER or COLOR_ACCENT
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
draw.SimpleText("ВВЕСТИ", "DermaDefaultBold", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
manualBtn.DoClick = function()
|
||||
Derma_StringRequest(
|
||||
"Ввод частоты",
|
||||
"Введите частоту рации (от " .. radioPlugin.minFrequency .. " до " .. radioPlugin.maxFrequency .. "):",
|
||||
tostring(currentFreq),
|
||||
function(text)
|
||||
local freq = tonumber(text)
|
||||
if freq then
|
||||
freq = math.Clamp(freq, radioPlugin.minFrequency, radioPlugin.maxFrequency)
|
||||
|
||||
net.Start("ixRadioSetFrequency")
|
||||
net.WriteFloat(freq)
|
||||
net.SendToServer()
|
||||
|
||||
freqSlider:SetValue(freq)
|
||||
LocalPlayer():Notify("Частота рации изменена на: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", freq))
|
||||
else
|
||||
LocalPlayer():Notify("Неверный формат частоты!")
|
||||
end
|
||||
end,
|
||||
function() end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- Кнопки включения/выключения рации (справа от меню рации)
|
||||
if radioPlugin then
|
||||
local controlsWidth = 150
|
||||
local controlsHeight = 200
|
||||
|
||||
local controlsPanel = vgui.Create("DPanel", basePanel)
|
||||
controlsPanel:SetSize(controlsWidth, controlsHeight)
|
||||
controlsPanel:SetPos(padding + radioWidth + 10, padding)
|
||||
controlsPanel.Paint = function(s, w, h)
|
||||
-- Основной фон
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
|
||||
|
||||
-- Заголовок
|
||||
draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawLine(0, 45, w, 45)
|
||||
|
||||
-- Текст заголовка
|
||||
draw.SimpleText("РАЦИЯ", "DermaDefault", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Декоративная линия
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(10, 43, w-20, 2)
|
||||
|
||||
-- Внешняя граница
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
-- Кнопка включения прослушивания
|
||||
local listenBtn = vgui.Create("DButton", controlsPanel)
|
||||
listenBtn:SetPos(10, 60)
|
||||
listenBtn:SetSize(controlsWidth - 20, 40)
|
||||
listenBtn:SetText("")
|
||||
listenBtn.Paint = function(s, w, h)
|
||||
local isListening = radioPlugin:IsListening(LocalPlayer())
|
||||
local col = isListening and COLOR_ACCENT or COLOR_BG_MEDIUM
|
||||
if s:IsHovered() then
|
||||
col = isListening and COLOR_ACCENT_HOVER or COLOR_PRIMARY_HOVER
|
||||
end
|
||||
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
local status = isListening and "ВКЛ" or "ВЫКЛ"
|
||||
draw.SimpleText("Прослушивание", "DermaDefault", w/2, h/2 - 7, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText(status, "DermaDefaultBold", w/2, h/2 + 7, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
listenBtn.DoClick = function()
|
||||
net.Start("ixRadioToggleListen")
|
||||
net.SendToServer()
|
||||
|
||||
timer.Simple(0.1, function()
|
||||
local status = radioPlugin:IsListening(LocalPlayer()) and "включено" or "выключено"
|
||||
LocalPlayer():Notify("Прослушивание рации " .. status)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Кнопка включения передачи
|
||||
local transmitBtn = vgui.Create("DButton", controlsPanel)
|
||||
transmitBtn:SetPos(10, 110)
|
||||
transmitBtn:SetSize(controlsWidth - 20, 40)
|
||||
transmitBtn:SetText("")
|
||||
transmitBtn.Paint = function(s, w, h)
|
||||
local isTransmitting = radioPlugin:IsTransmitting(LocalPlayer())
|
||||
local col = isTransmitting and COLOR_ACCENT or COLOR_BG_MEDIUM
|
||||
if s:IsHovered() then
|
||||
col = isTransmitting and COLOR_ACCENT_HOVER or COLOR_PRIMARY_HOVER
|
||||
end
|
||||
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
local status = isTransmitting and "ВКЛ" or "ВЫКЛ"
|
||||
draw.SimpleText("Передача", "DermaDefault", w/2, h/2 - 7, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText(status, "DermaDefaultBold", w/2, h/2 + 7, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
transmitBtn.DoClick = function()
|
||||
net.Start("ixRadioToggleTransmit")
|
||||
net.SendToServer()
|
||||
|
||||
timer.Simple(0.1, function()
|
||||
local status = radioPlugin:IsTransmitting(LocalPlayer()) and "включена" or "выключена"
|
||||
LocalPlayer():Notify("Передача рации " .. status)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
animMenu = basePanel
|
||||
animMenuOpen = true
|
||||
end
|
||||
|
||||
-- Блокировка стандартного Context Menu и открытие нашего
|
||||
hook.Add("OnContextMenuOpen", "AnimationMenuOverride", function()
|
||||
-- Проверка на Alt+C для администраторов (открывает стандартное меню)
|
||||
if input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) then
|
||||
local client = LocalPlayer()
|
||||
if IsValid(client) and client:GetCharacter() then
|
||||
-- Проверка на права администратора
|
||||
if client:IsAdmin() or client:IsSuperAdmin() then
|
||||
return -- Пропускаем и открываем стандартное меню
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not animMenuOpen then
|
||||
CreateAnimationMenu()
|
||||
end
|
||||
return false -- Блокируем стандартное меню
|
||||
end)
|
||||
|
||||
-- Закрытие меню при закрытии Context Menu
|
||||
hook.Add("OnContextMenuClose", "AnimationMenuClose", function()
|
||||
if animMenuOpen and IsValid(animMenu) then
|
||||
animMenu:Remove()
|
||||
animMenuOpen = false
|
||||
end
|
||||
end)
|
||||
|
||||
-- Подсказка на HUD
|
||||
--hook.Add("HUDPaint", "AnimationMenuHint", function()
|
||||
-- if not animMenuOpen then
|
||||
-- draw.SimpleTextOutlined("C - Открыть меню (анимации + рация)", "DermaDefault", ScrW() - 10, ScrH() - 30, COLOR_TEXT_DIM, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER, 1, Color(0, 0, 0, 200))
|
||||
-- end
|
||||
--end)
|
||||
@@ -0,0 +1,192 @@
|
||||
local PLUGIN = PLUGIN
|
||||
PLUGIN.name = "Animation Context Menu"
|
||||
PLUGIN.author = "RefoselTeamWork"
|
||||
PLUGIN.description = "Замена стандартного Context Menu на меню выбора анимаций"
|
||||
|
||||
PLUGIN.AnimationTable = {
|
||||
[1] = {name = "Помахать руками", anim = "rp_wave", loop = false},
|
||||
[2] = {name = "Помахать рукой", anim = "gesture_wave_original", loop = false},
|
||||
[3] = {name = "Показать пальцем вперед", anim = "rp_point", loop = false},
|
||||
[4] = {name = "Показать пальцем назад", anim = "rp_point_back", loop = false},
|
||||
[5] = {name = "Поправить галстук", anim = "menu_gman", loop = false},
|
||||
[6] = {name = "Приседания", anim = "rp_sport1_loop", loop = true},
|
||||
[7] = {name = "Отжимания", anim = "rp_sport2_loop", loop = true},
|
||||
[8] = {name = "Подъем корпуса", anim = "rp_sport3_loop", loop = true},
|
||||
[9] = {name = "Берпи", anim = "rp_sport4_loop", loop = true},
|
||||
[10] = {name = "Стоять злобно", anim = "rp_angry_loop", loop = true},
|
||||
[11] = {name = "Стоять напуганно", anim = "idle_all_scared", loop = true},
|
||||
[12] = {name = "Стоять, сложив руки", anim = "pose_standing_01", loop = true},
|
||||
[13] = {name = "Стоять, руки на поясе", anim = "pose_standing_02", loop = true},
|
||||
[14] = {name = "Стоять, держась за пояс", anim = "rp_cop_idle", loop = true},
|
||||
[15] = {name = "Сидеть, рука на колене", anim = "pose_ducking_01", loop = true},
|
||||
[16] = {name = "Сидеть в позе лотоса", anim = "pose_ducking_02", loop = true},
|
||||
[17] = {name = "Сидеть в сонном состоянии", anim = "rp_sit_loop", loop = true},
|
||||
[18] = {name = "Лежать в плохом состоянии", anim = "rp_injured_loop", loop = true},
|
||||
[19] = {name = "Танец 1", anim = "rp_dance1_loop", loop = true},
|
||||
[20] = {name = "Танец 2", anim = "rp_dance2_loop", loop = true},
|
||||
[21] = {name = "Танец 3", anim = "rp_dance3_loop", loop = true},
|
||||
[23] = {name = "Согласие", anim = "gesture_agree_original", loop = false},
|
||||
[24] = {name = "Несогласие", anim = "gesture_disagree_original", loop = false},
|
||||
[25] = {name = "Позвать с собой", anim = "gesture_becon_original", loop = false},
|
||||
[26] = {name = "Поклониться", anim = "gesture_bow_original", loop = false},
|
||||
[27] = {name = "Отдать честь", anim = "gesture_salute_original", loop = false},
|
||||
[28] = {name = "Пить кофе", anim = "rp_drinking", loop = true, prop = {model = "models/props_junk/garbage_coffeemug001a.mdl", spawnfunc = function(mod, ent)
|
||||
local att = ent:LookupAttachment("anim_attachment_RH")
|
||||
local lvec = Vector(1,0,1)
|
||||
|
||||
timer.Create("WhileThatPropExist"..ent:EntIndex(), 0, 0, function()
|
||||
if IsValid(mod) then
|
||||
local tab = ent:GetAttachment(att)
|
||||
|
||||
if (tab) then
|
||||
mod:SetPos(tab.Pos+tab.Ang:Up()*lvec.z+tab.Ang:Right()*lvec.y+tab.Ang:Forward()*lvec.x)
|
||||
mod:SetAngles(tab.Ang)
|
||||
end
|
||||
|
||||
if !IsValid(ent) or !ent:Alive() then
|
||||
timer.Remove("WhileThatPropExist"..ent:EntIndex())
|
||||
mod:Remove()
|
||||
end
|
||||
else
|
||||
timer.Remove("WhileThatPropExist"..ent:EntIndex())
|
||||
end
|
||||
end)
|
||||
end}},
|
||||
[29] = {name = "Смотреть на объект", anim = "rp_medic_idle", loop = true},
|
||||
[30] = {name = "Записывать в блокнот", anim = "rp_writing", loop = true, prop = {model = "models/props_lab/clipboard.mdl", spawnfunc = function(mod, ent)
|
||||
local att = ent:LookupAttachment("anim_attachment_RH")
|
||||
local lvec = Vector(2,-2,2)
|
||||
|
||||
timer.Create("WhileThatPropExist"..ent:EntIndex(), 0, 0, function()
|
||||
if IsValid(mod) then
|
||||
local tab = ent:GetAttachment(att)
|
||||
|
||||
if (tab) then
|
||||
mod:SetPos(tab.Pos+tab.Ang:Up()*lvec.z+tab.Ang:Right()*lvec.y+tab.Ang:Forward()*lvec.x)
|
||||
mod:SetAngles(tab.Ang+Angle(140,0,-100))
|
||||
end
|
||||
|
||||
if !IsValid(ent) or !ent:Alive() then
|
||||
timer.Remove("WhileThatPropExist"..ent:EntIndex())
|
||||
mod:Remove()
|
||||
end
|
||||
else
|
||||
timer.Remove("WhileThatPropExist"..ent:EntIndex())
|
||||
end
|
||||
end)
|
||||
end}},
|
||||
[31] = {name = "Спать", anim = "rp_sleep", loop = true},
|
||||
[32] = {name = "Воинское приветствие", anim = "rp_salute1", loop = true, loop_stop = true},
|
||||
[33] = {name = "Танец в присядь", anim = "rp_dance_squat", loop = true},
|
||||
}
|
||||
|
||||
if SERVER then
|
||||
concommand.Add("rp_set_taunt", function(ply, cmd, args)
|
||||
local arg = tonumber(args[1])
|
||||
if arg and arg > 0 and arg <= #PLUGIN.AnimationTable then
|
||||
local tab = PLUGIN.AnimationTable[arg]
|
||||
PLUGIN:SetTAnimation(ply, tab.anim, not tab.loop, arg)
|
||||
end
|
||||
end)
|
||||
|
||||
function PLUGIN:SetTAnimation(ply, anim, autostop, id)
|
||||
ply:SetNW2String('TauntAnim', anim)
|
||||
ply:SetNW2Float('TauntID', id)
|
||||
ply:SetNW2Float('TAnimDelay', select(2, ply:LookupSequence(anim)))
|
||||
ply:SetNW2Float('TAnimStartTime', CurTime())
|
||||
ply:SetCycle(0)
|
||||
|
||||
local wep = ply:GetActiveWeapon()
|
||||
if IsValid(wep) and anim != "" then
|
||||
ply.TauntPreviousWeapon = wep:GetClass()
|
||||
ply:SetActiveWeapon(nil)
|
||||
elseif anim == "" and isstring(ply.TauntPreviousWeapon) then
|
||||
ply:SelectWeapon(ply.TauntPreviousWeapon)
|
||||
ply.TauntPreviousWeapon = nil
|
||||
end
|
||||
|
||||
if autostop then
|
||||
local delay = select(2, ply:LookupSequence(anim))
|
||||
timer.Create("TauntAGRP"..ply:EntIndex(), delay, 1, function()
|
||||
if not IsValid(ply) then return end
|
||||
local anim2 = ply:GetNW2String('TauntAnim')
|
||||
if anim == anim2 then
|
||||
PLUGIN:SetTAnimation(ply, "")
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerSwitchWeapon(ply, oldWeapon, newWeapon)
|
||||
local str = ply:GetNW2String('TauntAnim')
|
||||
if str != "" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:Think()
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
local str = ply:GetNW2String('TauntAnim')
|
||||
if str != "" and (ply:InVehicle() or not ply:Alive() or ply:WaterLevel() >= 2) then
|
||||
PLUGIN:SetTAnimation(ply, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
CreateConVar("rp_taunt_firstperson", 0, FCVAR_ARCHIVE, "", 0, 1)
|
||||
CreateConVar("rp_taunt_realistic_firstperson", 0, FCVAR_ARCHIVE, "", 0, 1)
|
||||
end
|
||||
|
||||
function PLUGIN:SetupMove(ply, mvd, cmd)
|
||||
local str = ply:GetNW2String('TauntAnim')
|
||||
if str != "" then
|
||||
mvd:SetMaxSpeed(1)
|
||||
mvd:SetMaxClientSpeed(1)
|
||||
if SERVER and ply:KeyDown(IN_SPEED) then
|
||||
PLUGIN:SetTAnimation(ply, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:CalcMainActivity(ply, vel)
|
||||
local str = ply:GetNW2String('TauntAnim')
|
||||
local num = ply:GetNW2Float('TAnimDelay')
|
||||
local id = ply:GetNW2Float('TauntID')
|
||||
local st = ply:GetNW2Float('TAnimStartTime')
|
||||
|
||||
if str != "" and ply:Alive() then
|
||||
local ls = PLUGIN.AnimationTable[id] and PLUGIN.AnimationTable[id].loop_stop
|
||||
|
||||
if ply:GetCycle() >= 1 then
|
||||
if not ls then
|
||||
ply:SetCycle(0)
|
||||
if SERVER then
|
||||
ply:SetNW2Float('TAnimStartTime', CurTime())
|
||||
end
|
||||
end
|
||||
else
|
||||
ply:SetCycle((CurTime()-st)/num)
|
||||
|
||||
local tab = PLUGIN.AnimationTable[id] and PLUGIN.AnimationTable[id].prop
|
||||
if CLIENT and istable(tab) and (not IsValid(ply.TauntProp) or ply.TauntProp:GetModel() != tab.model) then
|
||||
if IsValid(ply.TauntProp) then
|
||||
ply.TauntProp:Remove()
|
||||
end
|
||||
ply.TauntProp = ClientsideModel(tab.model)
|
||||
tab.spawnfunc(ply.TauntProp, ply)
|
||||
elseif CLIENT and not istable(tab) and IsValid(ply.TauntProp) then
|
||||
ply.TauntProp:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
return -1, ply:LookupSequence(str)
|
||||
else
|
||||
if SERVER then
|
||||
ply:SetNW2Float('TauntID', 0)
|
||||
elseif CLIENT and IsValid(ply.TauntProp) then
|
||||
ply.TauntProp:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
706
garrysmod/gamemodes/militaryrp/plugins/arsenal/cl_plugin.lua
Normal file
706
garrysmod/gamemodes/militaryrp/plugins/arsenal/cl_plugin.lua
Normal file
@@ -0,0 +1,706 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
local COLOR_BG_DARK = Color(3, 5, 4)
|
||||
local COLOR_BG_MEDIUM = Color(8, 12, 10)
|
||||
local COLOR_BG_LIGHT = Color(12, 18, 14)
|
||||
local COLOR_PRIMARY = Color(27, 94, 32)
|
||||
local COLOR_PRIMARY_DARK = Color(15, 60, 18)
|
||||
local COLOR_ACCENT = Color(56, 102, 35)
|
||||
local COLOR_TEXT_PRIMARY = Color(165, 214, 167)
|
||||
local COLOR_TEXT_SECONDARY = Color(102, 187, 106)
|
||||
local COLOR_DANGER = Color(136, 14, 14)
|
||||
local COLOR_WARNING = Color(191, 130, 0)
|
||||
local COLOR_BORDER = Color(15, 60, 18, 60)
|
||||
|
||||
if not draw.Circle then
|
||||
function draw.Circle(x, y, radius, color)
|
||||
local segmentCount = math.max(16, radius)
|
||||
surface.SetDrawColor(color or color_white)
|
||||
|
||||
local circle = {}
|
||||
for i = 0, segmentCount do
|
||||
local angle = math.rad((i / segmentCount) * 360)
|
||||
table.insert(circle, {
|
||||
x = x + math.cos(angle) * radius,
|
||||
y = y + math.sin(angle) * radius
|
||||
})
|
||||
end
|
||||
surface.DrawPoly(circle)
|
||||
end
|
||||
end
|
||||
|
||||
if not surface.DrawCircle then
|
||||
function surface.DrawCircle(x, y, radius, color)
|
||||
draw.Circle(x, y, radius, color)
|
||||
end
|
||||
end
|
||||
local COLOR_TEXT_SECONDARY = Color(174, 213, 129)
|
||||
local COLOR_DANGER = Color(229, 57, 53)
|
||||
local COLOR_WARNING = Color(255, 193, 7)
|
||||
local COLOR_BORDER = Color(46, 125, 50, 100)
|
||||
|
||||
net.Receive("ixArsenalOpen", function()
|
||||
local supply = net.ReadUInt(32)
|
||||
local factionID = net.ReadUInt(8)
|
||||
local weaponsData = net.ReadTable()
|
||||
local weaponHas = net.ReadTable()
|
||||
local armorData = net.ReadTable()
|
||||
local freeRemain = net.ReadUInt(32)
|
||||
|
||||
if not weaponsData or type(weaponsData) ~= "table" then weaponsData = {} end
|
||||
if not weaponHas or type(weaponHas) ~= "table" then weaponHas = {} end
|
||||
|
||||
if IsValid(PLUGIN.menu) then
|
||||
PLUGIN.menu:Remove()
|
||||
end
|
||||
|
||||
local scrW, scrH = ScrW(), ScrH()
|
||||
local frame = vgui.Create("DFrame")
|
||||
frame:SetSize(scrW, scrH)
|
||||
frame:SetPos(0, 0)
|
||||
frame:SetTitle("")
|
||||
frame:SetDraggable(false)
|
||||
frame:ShowCloseButton(false)
|
||||
frame:MakePopup()
|
||||
|
||||
frame.Paint = function(s, w, h)
|
||||
surface.SetDrawColor(COLOR_BG_DARK)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
|
||||
local gradHeight = 300
|
||||
for i = 0, gradHeight do
|
||||
local alpha = (1 - i/gradHeight) * 40
|
||||
surface.SetDrawColor(COLOR_PRIMARY.r, COLOR_PRIMARY.g, COLOR_PRIMARY.b, alpha)
|
||||
surface.DrawRect(0, i, w, 1)
|
||||
end
|
||||
|
||||
surface.SetDrawColor(COLOR_BG_MEDIUM)
|
||||
surface.DrawRect(0, 0, w, 100)
|
||||
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawRect(0, 100, w, 3)
|
||||
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
for i = 0, w, 200 do
|
||||
surface.DrawRect(i, 0, 1, 100)
|
||||
end
|
||||
|
||||
draw.SimpleText("◆ ВОЕННЫЙ АРСЕНАЛ ◆", "ixMenuButtonFont", w/2, 35, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("СИСТЕМА СНАБЖЕНИЯ", "ixSmallFont", w/2, 68, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
local closeBtn = vgui.Create("DButton", frame)
|
||||
closeBtn:SetText("")
|
||||
closeBtn:SetSize(42, 42)
|
||||
closeBtn:SetPos(scrW - 60, 18)
|
||||
closeBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7)
|
||||
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
|
||||
surface.SetDrawColor(COLOR_BG_DARK)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
surface.SetDrawColor(COLOR_TEXT_PRIMARY)
|
||||
surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7)
|
||||
surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7)
|
||||
|
||||
if s:IsHovered() then
|
||||
surface.SetDrawColor(255, 255, 255, 30)
|
||||
draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30))
|
||||
end
|
||||
end
|
||||
closeBtn.DoClick = function() frame:Close() end
|
||||
|
||||
local infoPanel = vgui.Create("DPanel", frame)
|
||||
infoPanel:SetSize(scrW - 80, 70)
|
||||
infoPanel:SetPos(40, 115)
|
||||
infoPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
|
||||
local supplyBox = vgui.Create("DPanel", infoPanel)
|
||||
supplyBox:SetSize(infoPanel:GetWide() / 2 - 10, infoPanel:GetTall())
|
||||
supplyBox:SetPos(5, 0)
|
||||
supplyBox.Paint = function(s, w, h)
|
||||
draw.SimpleText("ОЧКИ СНАБЖЕНИЯ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
draw.SimpleText(tostring(supply), "ixMenuButtonFont", 20, 32, COLOR_ACCENT, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
draw.NoTexture()
|
||||
surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
local currencySymbol = "$"
|
||||
if PLUGIN and PLUGIN.config and PLUGIN.config.currencySymbol and factionID then
|
||||
currencySymbol = PLUGIN.config.currencySymbol[factionID] or currencySymbol
|
||||
end
|
||||
|
||||
local function formatTime(seconds)
|
||||
seconds = math.max(0, tonumber(seconds) or 0)
|
||||
if seconds <= 0 then return "Готово" end
|
||||
local h = math.floor(seconds / 3600)
|
||||
local m = math.floor((seconds % 3600) / 60)
|
||||
local s = seconds % 60
|
||||
if h > 0 then
|
||||
return string.format("%02d:%02d:%02d", h, m, s)
|
||||
else
|
||||
return string.format("%02d:%02d", m, s)
|
||||
end
|
||||
end
|
||||
|
||||
local cooldownBox = vgui.Create("DPanel", infoPanel)
|
||||
cooldownBox:SetSize(infoPanel:GetWide() / 2 - 10, infoPanel:GetTall())
|
||||
cooldownBox:SetPos(infoPanel:GetWide() / 2 + 5, 0)
|
||||
cooldownBox.Paint = function(s, w, h)
|
||||
draw.SimpleText("БЕСПЛАТНОЕ СНАРЯЖЕНИЕ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
|
||||
local timeText = formatTime(freeRemain)
|
||||
local timeColor = freeRemain <= 0 and COLOR_PRIMARY or COLOR_WARNING
|
||||
draw.SimpleText(timeText, "ixMenuButtonFont", 20, 32, timeColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
|
||||
if freeRemain > 0 then
|
||||
surface.SetDrawColor(COLOR_WARNING)
|
||||
draw.NoTexture()
|
||||
surface.DrawCircle(w - 35, h/2, 12, COLOR_WARNING)
|
||||
else
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
draw.NoTexture()
|
||||
surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY)
|
||||
end
|
||||
end
|
||||
|
||||
local colWidth = (scrW - 120) / 2
|
||||
local startY = 205
|
||||
|
||||
-- Панель оружия
|
||||
local weaponsPanel = vgui.Create("DPanel", frame)
|
||||
weaponsPanel:SetSize(colWidth, scrH - startY - 40)
|
||||
weaponsPanel:SetPos(40, startY)
|
||||
weaponsPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
|
||||
local weaponsHeader = vgui.Create("DPanel", weaponsPanel)
|
||||
weaponsHeader:SetSize(weaponsPanel:GetWide(), 45)
|
||||
weaponsHeader:SetPos(0, 0)
|
||||
weaponsHeader.Paint = function(s, w, h)
|
||||
draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false)
|
||||
|
||||
draw.SimpleText("▣ ОРУЖИЕ", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Декоративная линия
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(0, h-2, w, 2)
|
||||
end
|
||||
|
||||
local weaponsScroll = vgui.Create("DScrollPanel", weaponsPanel)
|
||||
weaponsScroll:SetSize(weaponsPanel:GetWide() - 20, weaponsPanel:GetTall() - 60)
|
||||
weaponsScroll:SetPos(10, 50)
|
||||
|
||||
local sbar = weaponsScroll:GetVBar()
|
||||
sbar:SetHideButtons(true)
|
||||
function sbar:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
|
||||
end
|
||||
function sbar.btnGrip:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
-- Функция для создания окна выбора оружия из категории
|
||||
local function OpenCategoryWeapons(categoryName, categoryIcon, categoryWeapons)
|
||||
local catFrame = vgui.Create("DFrame")
|
||||
catFrame:SetSize(800, 600)
|
||||
catFrame:Center()
|
||||
catFrame:SetTitle("")
|
||||
catFrame:SetDraggable(true)
|
||||
catFrame:ShowCloseButton(false)
|
||||
catFrame:MakePopup()
|
||||
|
||||
catFrame.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
|
||||
surface.SetDrawColor(COLOR_PRIMARY)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 3)
|
||||
|
||||
-- Заголовок
|
||||
draw.RoundedBoxEx(8, 0, 0, w, 60, COLOR_PRIMARY_DARK, true, true, false, false)
|
||||
draw.SimpleText(categoryIcon .. " " .. categoryName, "ixMenuButtonFont", w/2, 30, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(0, 60, w, 2)
|
||||
end
|
||||
|
||||
-- Кнопка закрытия
|
||||
local catCloseBtn = vgui.Create("DButton", catFrame)
|
||||
catCloseBtn:SetText("")
|
||||
catCloseBtn:SetSize(35, 35)
|
||||
catCloseBtn:SetPos(catFrame:GetWide() - 45, 12)
|
||||
catCloseBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7)
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(COLOR_TEXT_PRIMARY)
|
||||
surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7)
|
||||
surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7)
|
||||
end
|
||||
catCloseBtn.DoClick = function() catFrame:Close() end
|
||||
|
||||
-- Скролл с оружием
|
||||
local catScroll = vgui.Create("DScrollPanel", catFrame)
|
||||
catScroll:SetSize(catFrame:GetWide() - 30, catFrame:GetTall() - 85)
|
||||
catScroll:SetPos(15, 70)
|
||||
|
||||
local catSbar = catScroll:GetVBar()
|
||||
catSbar:SetHideButtons(true)
|
||||
function catSbar:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
|
||||
end
|
||||
function catSbar.btnGrip:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
-- Создаем карточки оружия
|
||||
for class, data in SortedPairsByMemberValue(categoryWeapons, "name", true) do
|
||||
local has = weaponHas[class]
|
||||
local row = vgui.Create("DPanel", catScroll)
|
||||
row:SetSize(catScroll:GetWide() - 10, 110)
|
||||
row:Dock(TOP)
|
||||
row:DockMargin(0, 0, 0, 10)
|
||||
|
||||
local isHovered = false
|
||||
row.Paint = function(s, w, h)
|
||||
local bgColor = has and Color(45, 25, 20) or COLOR_BG_LIGHT
|
||||
if isHovered then
|
||||
bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10)
|
||||
end
|
||||
draw.RoundedBox(6, 0, 0, w, h, bgColor)
|
||||
|
||||
local statusColor = has and Color(205, 127, 50) or COLOR_PRIMARY
|
||||
draw.RoundedBoxEx(6, 0, 0, 5, h, statusColor, true, false, true, false)
|
||||
|
||||
surface.SetDrawColor(has and Color(205, 127, 50, 80) or COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
if isHovered then
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(5, h-3, w-10, 3)
|
||||
end
|
||||
end
|
||||
|
||||
row.OnCursorEntered = function() isHovered = true end
|
||||
row.OnCursorExited = function() isHovered = false end
|
||||
|
||||
-- Название
|
||||
local nameLabel = vgui.Create("DLabel", row)
|
||||
nameLabel:SetText(data.name or class)
|
||||
nameLabel:SetFont("ixSmallFont")
|
||||
nameLabel:SetTextColor(COLOR_TEXT_PRIMARY)
|
||||
nameLabel:SetPos(15, 10)
|
||||
nameLabel:SizeToContents()
|
||||
|
||||
-- Цена
|
||||
local money = data.moneyPrice or 0
|
||||
local supply = data.supplyPrice or 0
|
||||
local priceLabel = vgui.Create("DLabel", row)
|
||||
local priceText = ""
|
||||
if (money or 0) <= 0 and (supply or 0) <= 0 then
|
||||
priceText = "★ БЕСПЛАТНО"
|
||||
else
|
||||
local parts = {}
|
||||
if money > 0 then table.insert(parts, currencySymbol .. tostring(money)) end
|
||||
if supply > 0 then table.insert(parts, tostring(supply) .. " ОС") end
|
||||
priceText = table.concat(parts, " • ")
|
||||
end
|
||||
priceLabel:SetText(priceText)
|
||||
priceLabel:SetFont("ixSmallFont")
|
||||
priceLabel:SetTextColor(money <= 0 and supply <= 0 and COLOR_ACCENT or COLOR_TEXT_PRIMARY)
|
||||
priceLabel:SetPos(15, 40)
|
||||
priceLabel:SizeToContents()
|
||||
|
||||
-- Донат метка
|
||||
if data.donate then
|
||||
local donateLabel = vgui.Create("DLabel", row)
|
||||
donateLabel:SetText("★ ПРЕМИУМ")
|
||||
donateLabel:SetFont("ixSmallFont")
|
||||
donateLabel:SetTextColor(COLOR_WARNING)
|
||||
donateLabel:SetPos(15, 70)
|
||||
donateLabel:SizeToContents()
|
||||
end
|
||||
|
||||
-- Кнопка действия
|
||||
local btn = vgui.Create("DButton", row)
|
||||
btn:SetSize(120, 40)
|
||||
btn:SetPos(row:GetWide() - 130, 35)
|
||||
btn:SetText("")
|
||||
|
||||
local btnText = has and "◄ ВЕРНУТЬ" or "ВЗЯТЬ ►"
|
||||
local btnColor = has and Color(165, 85, 60) or COLOR_PRIMARY
|
||||
local btnColorHover = has and Color(205, 105, 70) or COLOR_ACCENT
|
||||
|
||||
btn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and btnColorHover or btnColor
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
|
||||
if s:IsHovered() then
|
||||
surface.SetDrawColor(255, 255, 255, 30)
|
||||
draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30))
|
||||
end
|
||||
|
||||
surface.SetDrawColor(COLOR_BG_DARK)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
draw.SimpleText(btnText, "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
btn.DoClick = function()
|
||||
if has then
|
||||
Derma_Query("Вернуть снаряжение? Фракция получит 80% стоимости.", "Возврат", "Подтвердить", function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("return_weapon")
|
||||
net.WriteString(class)
|
||||
net.SendToServer()
|
||||
catFrame:Close()
|
||||
frame:Close()
|
||||
end, "Отмена", function() end)
|
||||
else
|
||||
local options = {}
|
||||
if (data.moneyPrice or 0) > 0 then table.insert(options, {text = "Купить за деньги", method = "money"}) end
|
||||
if (data.supplyPrice or 0) >= 0 then table.insert(options, {text = "Купить за очки", method = "supply"}) end
|
||||
|
||||
if #options == 0 then
|
||||
Derma_Message("Невозможно приобрести это оружие", "Ошибка", "OK")
|
||||
return
|
||||
end
|
||||
|
||||
if #options == 1 then
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_weapon")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[1].method)
|
||||
net.SendToServer()
|
||||
catFrame:Close()
|
||||
frame:Close()
|
||||
else
|
||||
local query = Derma_Query("Выберите способ оплаты:", "Оплата", options[1].text, function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_weapon")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[1].method)
|
||||
net.SendToServer()
|
||||
catFrame:Close()
|
||||
frame:Close()
|
||||
end, options[2].text, function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_weapon")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[2].method)
|
||||
net.SendToServer()
|
||||
catFrame:Close()
|
||||
frame:Close()
|
||||
end, "Отмена", function() end)
|
||||
|
||||
if IsValid(query) then
|
||||
query:ShowCloseButton(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Панель брони
|
||||
local armorPanel = vgui.Create("DPanel", frame)
|
||||
armorPanel:SetSize(colWidth, scrH - startY - 40)
|
||||
armorPanel:SetPos(scrW - colWidth - 40, startY)
|
||||
armorPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
|
||||
local armorHeader = vgui.Create("DPanel", armorPanel)
|
||||
armorHeader:SetSize(armorPanel:GetWide(), 45)
|
||||
armorHeader:SetPos(0, 0)
|
||||
armorHeader.Paint = function(s, w, h)
|
||||
draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false)
|
||||
|
||||
draw.SimpleText("⬢ ЗАЩИТА", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Декоративная линия
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(0, h-2, w, 2)
|
||||
end
|
||||
|
||||
local armorScroll = vgui.Create("DScrollPanel", armorPanel)
|
||||
armorScroll:SetSize(armorPanel:GetWide() - 20, armorPanel:GetTall() - 60)
|
||||
armorScroll:SetPos(10, 50)
|
||||
|
||||
local sbar2 = armorScroll:GetVBar()
|
||||
sbar2:SetHideButtons(true)
|
||||
function sbar2:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
|
||||
end
|
||||
function sbar2.btnGrip:Paint(w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
|
||||
end
|
||||
|
||||
-- Группировка оружия по категориям
|
||||
local categories = {
|
||||
{name = "Основное оружие", icon = "▣", key = "primary", weapons = {}},
|
||||
{name = "Пистолеты", icon = "▢", key = "secondary", weapons = {}},
|
||||
{name = "Гранаты", icon = "◆", key = "grenades", weapons = {}},
|
||||
{name = "Холодное оружие", icon = "▸", key = "melee", weapons = {}},
|
||||
{name = "Прочее", icon = "◇", key = "other", weapons = {}}
|
||||
}
|
||||
|
||||
-- Распределяем оружие по категориям
|
||||
for class, data in pairs(weaponsData) do
|
||||
if type(data) == "table" then
|
||||
local cat = data.category or "other"
|
||||
local added = false
|
||||
|
||||
if cat == "primary" then
|
||||
table.insert(categories[1].weapons, {class = class, data = data})
|
||||
added = true
|
||||
elseif cat == "secondary" then
|
||||
table.insert(categories[2].weapons, {class = class, data = data})
|
||||
added = true
|
||||
elseif cat == "grenade1" or cat == "grenade2" or cat == "grenade" then
|
||||
table.insert(categories[3].weapons, {class = class, data = data})
|
||||
added = true
|
||||
elseif cat == "melee" then
|
||||
table.insert(categories[4].weapons, {class = class, data = data})
|
||||
added = true
|
||||
end
|
||||
|
||||
if not added then
|
||||
table.insert(categories[5].weapons, {class = class, data = data})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Создаем кнопки категорий
|
||||
local totalWeapons = 0
|
||||
for _, category in ipairs(categories) do
|
||||
if #category.weapons > 0 then
|
||||
totalWeapons = totalWeapons + #category.weapons
|
||||
|
||||
local catButton = vgui.Create("DPanel", weaponsScroll)
|
||||
catButton:SetSize(weaponsScroll:GetWide() - 10, 80)
|
||||
catButton:Dock(TOP)
|
||||
catButton:DockMargin(0, 0, 0, 10)
|
||||
catButton:SetCursor("hand")
|
||||
|
||||
local isHovered = false
|
||||
catButton.Paint = function(s, w, h)
|
||||
local bgColor = COLOR_BG_LIGHT
|
||||
if isHovered then
|
||||
bgColor = Color(bgColor.r + 15, bgColor.g + 20, bgColor.b + 15)
|
||||
end
|
||||
draw.RoundedBox(6, 0, 0, w, h, bgColor)
|
||||
|
||||
-- Боковая полоска
|
||||
draw.RoundedBoxEx(6, 0, 0, 6, h, COLOR_PRIMARY, true, false, true, false)
|
||||
|
||||
-- Рамка
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
-- Акцентная линия при наведении
|
||||
if isHovered then
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(6, h-3, w-12, 3)
|
||||
end
|
||||
|
||||
-- Иконка категории
|
||||
draw.SimpleText(category.icon, "ixMenuButtonFont", 30, h/2, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Название категории
|
||||
draw.SimpleText(category.name, "ixSmallFont", 60, h/2 - 12, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Количество оружия
|
||||
draw.SimpleText(#category.weapons .. " ед.", "ixSmallFont", 60, h/2 + 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Стрелка
|
||||
draw.SimpleText("►", "ixSmallFont", w - 25, h/2, COLOR_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
catButton.OnCursorEntered = function() isHovered = true end
|
||||
catButton.OnCursorExited = function() isHovered = false end
|
||||
catButton.OnMousePressed = function()
|
||||
-- Подготовка данных для окна категории
|
||||
local categoryWeapons = {}
|
||||
for _, wpn in ipairs(category.weapons) do
|
||||
categoryWeapons[wpn.class] = wpn.data
|
||||
end
|
||||
OpenCategoryWeapons(category.name, category.icon, categoryWeapons)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Если нет оружия вообще
|
||||
if totalWeapons == 0 then
|
||||
local emptyLabel = vgui.Create("DPanel", weaponsScroll)
|
||||
emptyLabel:SetSize(weaponsScroll:GetWide(), 100)
|
||||
emptyLabel:Dock(TOP)
|
||||
emptyLabel:DockMargin(0, 20, 0, 0)
|
||||
emptyLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
|
||||
draw.SimpleText("⚠ НЕТ ДОСТУПНОГО ОРУЖИЯ", "ixSmallFont", w/2, h/2 - 10, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("Обратитесь к командованию", "ixSmallFont", w/2, h/2 + 15, Color(COLOR_TEXT_SECONDARY.r * 0.7, COLOR_TEXT_SECONDARY.g * 0.7, COLOR_TEXT_SECONDARY.b * 0.7), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
-- Заполнение брони
|
||||
if not armorData or type(armorData) ~= "table" then armorData = {} end
|
||||
if table.Count(armorData) > 0 then
|
||||
for class, data in SortedPairsByMemberValue(armorData, "name", true) do
|
||||
local row = vgui.Create("DPanel", armorScroll)
|
||||
row:SetSize(armorScroll:GetWide() - 10, 100)
|
||||
row:Dock(TOP)
|
||||
row:DockMargin(0, 0, 0, 10)
|
||||
|
||||
local isHovered = false
|
||||
row.Paint = function(s, w, h)
|
||||
local bgColor = COLOR_BG_LIGHT
|
||||
if isHovered then
|
||||
bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10)
|
||||
end
|
||||
draw.RoundedBox(6, 0, 0, w, h, bgColor)
|
||||
|
||||
-- Боковая полоска
|
||||
draw.RoundedBoxEx(6, 0, 0, 5, h, COLOR_ACCENT, true, false, true, false)
|
||||
|
||||
-- Рамка
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
-- Акцентная линия при наведении
|
||||
if isHovered then
|
||||
surface.SetDrawColor(COLOR_ACCENT)
|
||||
surface.DrawRect(5, h-3, w-10, 3)
|
||||
end
|
||||
end
|
||||
|
||||
row.OnCursorEntered = function() isHovered = true end
|
||||
row.OnCursorExited = function() isHovered = false end
|
||||
|
||||
-- Название брони
|
||||
local nameLabel = vgui.Create("DLabel", row)
|
||||
nameLabel:SetText(data.name or class)
|
||||
nameLabel:SetFont("ixSmallFont")
|
||||
nameLabel:SetTextColor(COLOR_TEXT_PRIMARY)
|
||||
nameLabel:SetPos(15, 10)
|
||||
nameLabel:SizeToContents()
|
||||
|
||||
-- Параметры брони
|
||||
local amountLabel = vgui.Create("DLabel", row)
|
||||
amountLabel:SetText("⬢ Защита: " .. tostring(data.amount or 0) .. " ед.")
|
||||
amountLabel:SetFont("ixSmallFont")
|
||||
amountLabel:SetTextColor(COLOR_TEXT_SECONDARY)
|
||||
amountLabel:SetPos(15, 35)
|
||||
amountLabel:SizeToContents()
|
||||
|
||||
-- Цена
|
||||
local moneyP = data.moneyPrice or 0
|
||||
local supplyP = data.supplyPrice or 0
|
||||
local priceText = ""
|
||||
if (moneyP or 0) <= 0 and (supplyP or 0) <= 0 then
|
||||
priceText = "★ БЕСПЛАТНО"
|
||||
else
|
||||
local parts = {}
|
||||
if moneyP > 0 then table.insert(parts, currencySymbol .. tostring(moneyP)) end
|
||||
if supplyP > 0 then table.insert(parts, tostring(supplyP) .. " ОС") end
|
||||
priceText = table.concat(parts, " • ")
|
||||
end
|
||||
local priceLabel = vgui.Create("DLabel", row)
|
||||
priceLabel:SetText(priceText)
|
||||
priceLabel:SetFont("ixSmallFont")
|
||||
priceLabel:SetTextColor(moneyP <= 0 and supplyP <= 0 and COLOR_ACCENT or COLOR_TEXT_PRIMARY)
|
||||
priceLabel:SetPos(15, 58)
|
||||
priceLabel:SizeToContents()
|
||||
|
||||
-- Кнопка покупки
|
||||
local btn = vgui.Create("DButton", row)
|
||||
btn:SetSize(120, 40)
|
||||
btn:SetPos(row:GetWide() - 130, 30)
|
||||
btn:SetText("")
|
||||
|
||||
btn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
|
||||
if s:IsHovered() then
|
||||
surface.SetDrawColor(255, 255, 255, 30)
|
||||
draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30))
|
||||
end
|
||||
|
||||
surface.SetDrawColor(COLOR_BG_DARK)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
|
||||
draw.SimpleText("КУПИТЬ ►", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
btn.DoClick = function()
|
||||
local options = {}
|
||||
if (data.moneyPrice or 0) > 0 then table.insert(options, {text = "Купить за деньги", method = "money"}) end
|
||||
if (data.supplyPrice or 0) >= 0 then table.insert(options, {text = "Купить за очки", method = "supply"}) end
|
||||
|
||||
if #options == 0 then
|
||||
Derma_Message("Невозможно приобрести эту броню", "Ошибка", "OK")
|
||||
return
|
||||
end
|
||||
|
||||
if #options == 1 then
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_armor")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[1].method)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
else
|
||||
local query = Derma_Query("Выберите способ оплаты:", "Оплата", options[1].text, function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_armor")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[1].method)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
end, options[2].text, function()
|
||||
net.Start("ixArsenalAction")
|
||||
net.WriteString("buy_armor")
|
||||
net.WriteString(class)
|
||||
net.WriteString(options[2].method)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
end, "Отмена", function() end)
|
||||
|
||||
if IsValid(query) then
|
||||
query:ShowCloseButton(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
local emptyLabel = vgui.Create("DPanel", armorScroll)
|
||||
emptyLabel:SetSize(armorScroll:GetWide(), 100)
|
||||
emptyLabel:Dock(TOP)
|
||||
emptyLabel:DockMargin(0, 20, 0, 0)
|
||||
emptyLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT)
|
||||
surface.SetDrawColor(COLOR_BORDER)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
|
||||
draw.SimpleText("⚠ НЕТ ДОСТУПНОЙ ЗАЩИТЫ", "ixSmallFont", w/2, h/2 - 10, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText("Обратитесь к командованию", "ixSmallFont", w/2, h/2 + 15, Color(COLOR_TEXT_SECONDARY.r * 0.7, COLOR_TEXT_SECONDARY.g * 0.7, COLOR_TEXT_SECONDARY.b * 0.7), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
PLUGIN.menu = frame
|
||||
end)
|
||||
@@ -0,0 +1,234 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Ящик с патронами"
|
||||
ENT.Author = "Server"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
ENT.Category = "[FT] Система арсенала"
|
||||
ENT.bNoPersist = true
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/trenches/prop/para_ammo/ammo_supply.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then
|
||||
phys:Wake()
|
||||
end
|
||||
|
||||
-- Таблица для хранения кулдаунов игроков
|
||||
self.playerCooldowns = {}
|
||||
end
|
||||
|
||||
-- Получить информацию о патронах для оружия
|
||||
function ENT:GetWeaponAmmoInfo(weapon)
|
||||
if not IsValid(weapon) then return nil end
|
||||
|
||||
local ammoType = weapon:GetPrimaryAmmoType()
|
||||
if ammoType == -1 then return nil end
|
||||
|
||||
local clipSize = weapon:GetMaxClip1()
|
||||
if clipSize <= 0 then return nil end
|
||||
|
||||
return {
|
||||
ammoType = ammoType,
|
||||
clipSize = clipSize,
|
||||
ammoName = game.GetAmmoName(ammoType) or "unknown"
|
||||
}
|
||||
end
|
||||
|
||||
-- Использование ящика
|
||||
function ENT:Use(activator, caller)
|
||||
if not IsValid(activator) or not activator:IsPlayer() then return end
|
||||
|
||||
-- Проверка кулдауна (1 секунда)
|
||||
local steamID = activator:SteamID()
|
||||
local now = CurTime()
|
||||
if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local char = activator:GetCharacter()
|
||||
if not char then
|
||||
activator:Notify("У вас нет активного персонажа")
|
||||
return
|
||||
end
|
||||
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then
|
||||
activator:Notify("Плагин арсенала не найден")
|
||||
return
|
||||
end
|
||||
|
||||
-- Проверка оружия в руках
|
||||
local weapon = activator:GetActiveWeapon()
|
||||
if not IsValid(weapon) then
|
||||
activator:Notify("Возьмите оружие в руки")
|
||||
return
|
||||
end
|
||||
|
||||
-- Получение информации о патронах
|
||||
local ammoInfo = self:GetWeaponAmmoInfo(weapon)
|
||||
local isMedicalKit = weapon:GetClass() == "tacrp_ifak"
|
||||
|
||||
if not ammoInfo and not isMedicalKit then
|
||||
activator:Notify("Это оружие не использует патроны")
|
||||
return
|
||||
end
|
||||
|
||||
-- Специальная логика для медицинской аптечки IFAK
|
||||
if isMedicalKit then
|
||||
-- Проверяем текущие запасы медицинских расходников
|
||||
local bandages = activator:GetAmmoCount(game.GetAmmoID("Bandages"))
|
||||
local quikclots = activator:GetAmmoCount(game.GetAmmoID("Quikclots"))
|
||||
local hemostats = activator:GetAmmoCount(game.GetAmmoID("Hemostats"))
|
||||
|
||||
local maxBandages = 4
|
||||
local maxQuikclots = 3
|
||||
local maxHemostats = 2
|
||||
|
||||
if bandages >= maxBandages and quikclots >= maxQuikclots and hemostats >= maxHemostats then
|
||||
activator:Notify("У вас уже полный комплект медицинских расходников")
|
||||
return
|
||||
end
|
||||
|
||||
-- Рассчитываем стоимость пополнения
|
||||
local costPerItem = 5 -- стоимость за единицу
|
||||
local totalCost = 0
|
||||
local itemsToGive = {}
|
||||
|
||||
if bandages < maxBandages then
|
||||
local needed = maxBandages - bandages
|
||||
totalCost = totalCost + needed * costPerItem
|
||||
itemsToGive["Bandages"] = needed
|
||||
end
|
||||
|
||||
if quikclots < maxQuikclots then
|
||||
local needed = maxQuikclots - quikclots
|
||||
totalCost = totalCost + needed * costPerItem
|
||||
itemsToGive["Quikclots"] = needed
|
||||
end
|
||||
|
||||
if hemostats < maxHemostats then
|
||||
local needed = maxHemostats - hemostats
|
||||
totalCost = totalCost + needed * costPerItem
|
||||
itemsToGive["Hemostats"] = needed
|
||||
end
|
||||
|
||||
-- Проверка достаточно ли очков снабжения
|
||||
if supply < totalCost then
|
||||
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
|
||||
return
|
||||
end
|
||||
|
||||
-- Выдаем расходники
|
||||
for ammoType, amount in pairs(itemsToGive) do
|
||||
activator:GiveAmmo(amount, ammoType, true)
|
||||
end
|
||||
|
||||
-- Списываем очки снабжения
|
||||
plugin:AddFactionSupply(factionID, -totalCost)
|
||||
|
||||
-- Устанавливаем кулдаун
|
||||
self.playerCooldowns[steamID] = now
|
||||
|
||||
-- Уведомление
|
||||
local itemList = {}
|
||||
for ammoType, amount in pairs(itemsToGive) do
|
||||
table.insert(itemList, string.format("%s: +%d", ammoType, amount))
|
||||
end
|
||||
activator:Notify(string.format("Получены медицинские расходники: %s", table.concat(itemList, ", ")))
|
||||
|
||||
-- Звуковой эффект
|
||||
self:EmitSound("items/ammo_pickup.wav")
|
||||
|
||||
-- Логирование
|
||||
print(string.format("[AMMOBOX] %s получил медицинские расходники. Стоимость: %d очков",
|
||||
activator:Nick(), totalCost))
|
||||
return
|
||||
end
|
||||
|
||||
-- Проверяем конфиг арсенала для этого оружия (одноразовые гранатомёты)
|
||||
local weaponClass = weapon:GetClass()
|
||||
local arsenalConfig = plugin.config and plugin.config.weapons and plugin.config.weapons[weaponClass]
|
||||
-- Детект по конфигу ИЛИ по типу патронов — rpg_round и PanzerFaust3 Rocket всегда одноразовые
|
||||
local launcherAmmoTypes = { ["rpg_round"] = true, ["PanzerFaust3 Rocket"] = true }
|
||||
local isOneShotLauncher = (arsenalConfig and (arsenalConfig.maxAmmo == 1 or arsenalConfig.maxCount == 1))
|
||||
or launcherAmmoTypes[ammoInfo.ammoName]
|
||||
|
||||
-- Проверка фракции
|
||||
local factionID = char:GetFaction()
|
||||
local supply = plugin:GetFactionSupply(factionID)
|
||||
supply = tonumber(supply) or 0
|
||||
|
||||
if isOneShotLauncher then
|
||||
-- Одноразовый гранатомёт: max 1 снаряд в запасе
|
||||
local currentAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
|
||||
if currentAmmo >= 1 then
|
||||
activator:Notify("У вас уже есть снаряд для этого оружия")
|
||||
return
|
||||
end
|
||||
|
||||
-- Стоимость 1 снаряда через costPerBullet
|
||||
local totalCost = 1
|
||||
if supply < totalCost then
|
||||
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
|
||||
return
|
||||
end
|
||||
|
||||
-- Выдаём ровно 1 снаряд через SetAmmo (GiveAmmo выдаёт минимум 6 для rpg_round)
|
||||
activator:SetAmmo(currentAmmo + 1, ammoInfo.ammoType)
|
||||
plugin:AddFactionSupply(factionID, -totalCost)
|
||||
self.playerCooldowns[steamID] = now
|
||||
|
||||
activator:Notify("Получен 1 снаряд")
|
||||
self:EmitSound("items/ammo_pickup.wav")
|
||||
else
|
||||
-- Обычное оружие: проверка реального количества патронов у игрока
|
||||
local currentAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
|
||||
local maxAllowedAmmo = ammoInfo.clipSize * 6 -- 6 магазинов запаса
|
||||
|
||||
if currentAmmo >= maxAllowedAmmo then
|
||||
local currentMags = math.floor(currentAmmo / ammoInfo.clipSize)
|
||||
activator:Notify(string.format("У вас уже максимум патронов (%d магазинов, 6/6)", currentMags))
|
||||
return
|
||||
end
|
||||
|
||||
-- Рассчитываем количество патронов (1 магазин)
|
||||
local totalAmmo = ammoInfo.clipSize
|
||||
local costPerBullet = 1
|
||||
local totalCost = totalAmmo * costPerBullet
|
||||
|
||||
if supply < totalCost then
|
||||
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
|
||||
return
|
||||
end
|
||||
|
||||
-- Выдаем патроны
|
||||
activator:GiveAmmo(totalAmmo, ammoInfo.ammoType, true)
|
||||
plugin:AddFactionSupply(factionID, -totalCost)
|
||||
self.playerCooldowns[steamID] = now
|
||||
|
||||
-- Подсчёт текущих магазинов после выдачи
|
||||
local newAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
|
||||
local newMags = math.floor(newAmmo / ammoInfo.clipSize)
|
||||
local magsStr = math.min(newMags, 6) .. "/6"
|
||||
activator:Notify(string.format("Получено %d %s (магазинов: %s)", totalAmmo,
|
||||
totalAmmo == 1 and "патрон" or (totalAmmo < 5 and "патрона" or "патронов"), magsStr))
|
||||
self:EmitSound("items/ammo_pickup.wav")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Арсенал"
|
||||
ENT.Author = "Server"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
ENT.Category = "[FT] Система арсенала"
|
||||
ENT.bNoPersist = true
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/ft/shkaf.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then phys:Wake() end
|
||||
end
|
||||
|
||||
function ENT:Use(activator, caller)
|
||||
if not IsValid(activator) or not activator:IsPlayer() then return end
|
||||
local char = activator:GetCharacter()
|
||||
if not char then return end
|
||||
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return end
|
||||
|
||||
local avail = plugin:GetAvailableWeapons(char)
|
||||
local weaponsData = {}
|
||||
local weaponHas = {}
|
||||
for class, data in pairs(avail) do
|
||||
weaponsData[class] = data
|
||||
weaponHas[class] = false
|
||||
end
|
||||
|
||||
for _, wep in ipairs(activator:GetWeapons()) do
|
||||
if IsValid(wep) then
|
||||
local c = wep:GetClass()
|
||||
if weaponHas[c] ~= nil then weaponHas[c] = true end
|
||||
end
|
||||
end
|
||||
|
||||
local supply = plugin:GetFactionSupply(char:GetFaction())
|
||||
supply = tonumber(supply) or 0
|
||||
|
||||
local freeRemain = plugin:GetFreeWeaponCooldownForPlayer(activator)
|
||||
local factionID = char:GetFaction()
|
||||
net.Start("ixArsenalOpen")
|
||||
net.WriteUInt(supply, 32)
|
||||
net.WriteUInt(factionID or 0, 8)
|
||||
net.WriteTable(weaponsData)
|
||||
net.WriteTable(weaponHas)
|
||||
net.WriteTable(plugin.config.armor or {})
|
||||
net.WriteUInt(math.max(0, tonumber(freeRemain) or 0), 32)
|
||||
net.Send(activator)
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
--local pos = self:GetPos() + Vector(0,0,10)
|
||||
--local ang = Angle(0, LocalPlayer():EyeAngles().y - 90, 90)
|
||||
--cam.Start3D2D(pos, ang, 0.1)
|
||||
-- draw.SimpleText("АРСЕНАЛ", "DermaDefaultBold", 0, 0, Color(200,200,200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
--cam.End3D2D()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,596 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Гардероб"
|
||||
ENT.Author = "Server"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
ENT.Category = "[FT] Система арсенала"
|
||||
ENT.bNoPersist = true
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/props_wasteland/controlroom_storagecloset001a.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then phys:Wake() end
|
||||
self.playerCooldowns = {}
|
||||
end
|
||||
|
||||
function ENT:GetModelBodygroups(model)
|
||||
local tempEnt = ents.Create("prop_dynamic")
|
||||
if not IsValid(tempEnt) then return {} end
|
||||
tempEnt:SetModel(model)
|
||||
tempEnt:Spawn()
|
||||
local bodygroups = {}
|
||||
for i = 0, tempEnt:GetNumBodyGroups() - 1 do
|
||||
local name = tempEnt:GetBodygroupName(i)
|
||||
local count = tempEnt:GetBodygroupCount(i)
|
||||
if count > 1 then
|
||||
bodygroups[i] = {name = name, count = count, index = i}
|
||||
end
|
||||
end
|
||||
tempEnt:Remove()
|
||||
return bodygroups
|
||||
end
|
||||
|
||||
function ENT:GetModelSkinCount(model)
|
||||
local tempEnt = ents.Create("prop_dynamic")
|
||||
if not IsValid(tempEnt) then return 0 end
|
||||
tempEnt:SetModel(model)
|
||||
tempEnt:Spawn()
|
||||
local skinCount = tempEnt:SkinCount() or 0
|
||||
tempEnt:Remove()
|
||||
return skinCount
|
||||
end
|
||||
|
||||
function ENT:IsBlacklisted(model, type, index)
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin or not plugin.config.wardrobeBlacklist then return false end
|
||||
local blacklist = plugin.config.wardrobeBlacklist[model]
|
||||
if not blacklist then return false end
|
||||
if type == "bodygroup" and blacklist.bodygroups then
|
||||
return table.HasValue(blacklist.bodygroups, index)
|
||||
elseif type == "skin" and blacklist.skins then
|
||||
return table.HasValue(blacklist.skins, index)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:Use(activator)
|
||||
if not IsValid(activator) or not activator:IsPlayer() then return end
|
||||
if not self.playerCooldowns then self.playerCooldowns = {} end
|
||||
local steamID = activator:SteamID()
|
||||
local now = CurTime()
|
||||
if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 2 then return end
|
||||
local char = activator:GetCharacter()
|
||||
if not char then return end
|
||||
local model = activator:GetModel()
|
||||
if not model then return end
|
||||
|
||||
local bodygroups = self:GetModelBodygroups(model)
|
||||
local skinCount = self:GetModelSkinCount(model)
|
||||
local availableBodygroups = {}
|
||||
for idx, data in pairs(bodygroups) do
|
||||
if not self:IsBlacklisted(model, "bodygroup", idx) then
|
||||
availableBodygroups[idx] = data
|
||||
end
|
||||
end
|
||||
local availableSkins = {}
|
||||
for i = 0, skinCount - 1 do
|
||||
if not self:IsBlacklisted(model, "skin", i) then
|
||||
table.insert(availableSkins, i)
|
||||
end
|
||||
end
|
||||
|
||||
local currentBodygroups = {}
|
||||
for idx in pairs(availableBodygroups) do
|
||||
currentBodygroups[idx] = activator:GetBodygroup(idx)
|
||||
end
|
||||
local currentSkin = activator:GetSkin()
|
||||
local currentPatch = char:GetData("patchIndex", 1)
|
||||
|
||||
self.playerCooldowns[steamID] = now
|
||||
net.Start("ixWardrobeOpen")
|
||||
net.WriteString(model)
|
||||
net.WriteTable(availableBodygroups)
|
||||
net.WriteTable(availableSkins)
|
||||
net.WriteTable(currentBodygroups)
|
||||
net.WriteUInt(currentSkin, 8)
|
||||
net.WriteUInt(currentPatch, 8)
|
||||
net.Send(activator)
|
||||
end
|
||||
|
||||
net.Receive("ixWardrobeApply", function(len, client)
|
||||
if not IsValid(client) then return end
|
||||
local char = client:GetCharacter()
|
||||
if not char then return end
|
||||
|
||||
local isWipe = net.ReadBool()
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
|
||||
if isWipe then
|
||||
client:SetSkin(0)
|
||||
char:SetData("skin", 0)
|
||||
char:SetData("patchIndex", 1)
|
||||
char:SetData("bodygroups", nil)
|
||||
|
||||
local bgs = client:GetBodyGroups()
|
||||
for _, bg in ipairs(bgs) do
|
||||
client:SetBodygroup(bg.id, 0)
|
||||
end
|
||||
|
||||
if plugin and plugin.config.patchIndex then
|
||||
client:SetSubMaterial(plugin.config.patchIndex, "")
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local bodygroups = net.ReadTable()
|
||||
local skin = net.ReadUInt(8)
|
||||
local patch = net.ReadUInt(8)
|
||||
local model = client:GetModel()
|
||||
|
||||
local faction = char:GetFaction()
|
||||
for idx, value in pairs(bodygroups) do
|
||||
if plugin and plugin.config.wardrobeAccess and plugin.config.wardrobeAccess[model] then
|
||||
local bAccess = plugin.config.wardrobeAccess[model][idx]
|
||||
if bAccess and bAccess[value] then
|
||||
if not table.HasValue(bAccess[value], faction) then
|
||||
value = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
client:SetBodygroup(idx, value)
|
||||
end
|
||||
|
||||
client:SetSkin(skin)
|
||||
char:SetData("bodygroups", bodygroups)
|
||||
char:SetData("skin", skin)
|
||||
char:SetData("patchIndex", patch)
|
||||
|
||||
if plugin and plugin.config.patchIndex and plugin.config.patchMaterials then
|
||||
local pMat = plugin.config.patchMaterials[patch]
|
||||
if pMat then
|
||||
client:SetSubMaterial(plugin.config.patchIndex, pMat)
|
||||
else
|
||||
client:SetSubMaterial(plugin.config.patchIndex, "")
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
local C_BG_D = Color(3, 5, 4)
|
||||
local C_BG_M = Color(8, 12, 10)
|
||||
local C_BG_L = Color(12, 18, 14)
|
||||
local C_PRI = Color(27, 94, 32)
|
||||
local C_PRI_H = Color(33, 110, 38)
|
||||
local C_ACC = Color(56, 102, 35)
|
||||
local C_ACC_H = Color(66, 120, 45)
|
||||
local C_BDR = Color(46, 125, 50, 80)
|
||||
local C_TXT_P = Color(165, 214, 167)
|
||||
local C_TXT_S = Color(129, 199, 132)
|
||||
local C_GRAD = Color(27, 94, 32, 15)
|
||||
local C_WARN = Color(150, 40, 40)
|
||||
local C_WARN_H = Color(180, 50, 50)
|
||||
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
end
|
||||
|
||||
net.Receive("ixWardrobeOpen", function()
|
||||
local model = net.ReadString()
|
||||
local bodygroups = net.ReadTable()
|
||||
local availableSkins = net.ReadTable()
|
||||
local currentBodygroups = net.ReadTable()
|
||||
local currentSkin = net.ReadUInt(8)
|
||||
local currentPatch = net.ReadUInt(8)
|
||||
if IsRecruit(ply) then
|
||||
ply:Notify("Новоприбывший не имеет доступа к гардеробу.")
|
||||
return
|
||||
end
|
||||
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return end
|
||||
|
||||
local frame = vgui.Create("DFrame")
|
||||
frame:SetSize(900, 600)
|
||||
frame:Center()
|
||||
frame:SetTitle("")
|
||||
frame:ShowCloseButton(false)
|
||||
frame:MakePopup()
|
||||
frame:SetBackgroundBlur(true)
|
||||
frame.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, C_BG_D)
|
||||
surface.SetDrawColor(C_GRAD)
|
||||
surface.DrawRect(0, 0, w, 3)
|
||||
draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawLine(0, 50, w, 50)
|
||||
draw.SimpleText("ГАРДЕРОБ", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
surface.SetDrawColor(C_ACC)
|
||||
surface.DrawRect(20, 48, 30, 2)
|
||||
surface.DrawRect(w-50, 48, 30, 2)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local closeBtn = vgui.Create("DButton", frame)
|
||||
closeBtn:SetPos(frame:GetWide() - 40, 10)
|
||||
closeBtn:SetSize(30, 30)
|
||||
closeBtn:SetText("")
|
||||
closeBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_ACC_H or C_ACC
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
draw.SimpleText("X", "DermaLarge", w/2, h/2-2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
closeBtn.DoClick = function() frame:Close() end
|
||||
|
||||
local modelPanel = vgui.Create("DModelPanel", frame)
|
||||
modelPanel:SetPos(20, 60)
|
||||
modelPanel:SetSize(400, 460)
|
||||
modelPanel:SetModel(model)
|
||||
|
||||
local oldPaint = modelPanel.Paint
|
||||
modelPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
oldPaint(s, w, h)
|
||||
end
|
||||
|
||||
modelPanel.LayoutEntity = function(s, ent)
|
||||
if not s.bAnimSet then
|
||||
local seq = ent:LookupSequence("idle_subtle")
|
||||
if seq <= 0 then seq = ent:LookupSequence("idle_all_01") end
|
||||
if seq > 0 then
|
||||
ent:SetSequence(seq)
|
||||
end
|
||||
s.bAnimSet = true
|
||||
end
|
||||
|
||||
if (s.bDragging) then
|
||||
local x, y = gui.MousePos()
|
||||
s.camAnims.rot.yaw = s.camAnims.rot.yaw + (x - s.lastX) * 0.8
|
||||
s.camAnims.rot.pitch = math.Clamp(s.camAnims.rot.pitch + (y - s.lastY) * 0.8, -45, 45)
|
||||
s.lastX, s.lastY = x, y
|
||||
end
|
||||
|
||||
local speed = FrameTime() * 10
|
||||
s.curZoom = Lerp(speed, s.curZoom, s.camAnims.zoom)
|
||||
s.curRot = LerpAngle(speed, s.curRot, s.camAnims.rot)
|
||||
|
||||
local eyePos = ent:GetPos() + Vector(0, 0, 40)
|
||||
s:SetCamPos(eyePos - s.curRot:Forward() * s.curZoom)
|
||||
s:SetLookAng(s.curRot)
|
||||
|
||||
ent:FrameAdvance((RealTime() - (s.lastTick or RealTime())) * 1)
|
||||
s.lastTick = RealTime()
|
||||
end
|
||||
|
||||
modelPanel.camAnims = { zoom = 60, rot = Angle(0, 180, 0) }
|
||||
modelPanel.curZoom = 60
|
||||
modelPanel.curRot = Angle(0, 180, 0)
|
||||
|
||||
local function SetCameraAngle(angle)
|
||||
if angle == "front" then
|
||||
modelPanel.camAnims.rot = Angle(0, 180, 0)
|
||||
elseif angle == "side" then
|
||||
modelPanel.camAnims.rot = Angle(0, 90, 0)
|
||||
elseif angle == "back" then
|
||||
modelPanel.camAnims.rot = Angle(0, 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
modelPanel.OnMousePressed = function(s, code)
|
||||
if code == MOUSE_LEFT then
|
||||
s.bDragging = true
|
||||
s.lastX, s.lastY = gui.MousePos()
|
||||
end
|
||||
end
|
||||
|
||||
modelPanel.OnMouseReleased = function(s, code)
|
||||
if code == MOUSE_LEFT then
|
||||
s.bDragging = false
|
||||
end
|
||||
end
|
||||
|
||||
modelPanel.OnCursorExited = function(s)
|
||||
s.bDragging = false
|
||||
end
|
||||
|
||||
modelPanel.OnMouseWheeled = function(s, delta)
|
||||
s.camAnims.zoom = math.Clamp(s.camAnims.zoom - delta * 15, 20, 120)
|
||||
end
|
||||
|
||||
timer.Simple(0.05, function()
|
||||
if IsValid(modelPanel) then SetCameraAngle("front") end
|
||||
end)
|
||||
|
||||
modelPanel.Think = function(s)
|
||||
local entity = s:GetEntity()
|
||||
if IsValid(entity) then
|
||||
for idx, value in pairs(currentBodygroups) do
|
||||
if entity:GetBodygroup(idx) ~= value then
|
||||
entity:SetBodygroup(idx, value)
|
||||
end
|
||||
end
|
||||
if entity:GetSkin() ~= currentSkin then
|
||||
entity:SetSkin(currentSkin)
|
||||
end
|
||||
|
||||
if plugin and plugin.config.patchIndex and plugin.config.patchMaterials then
|
||||
local pMat = plugin.config.patchMaterials[currentPatch]
|
||||
if pMat then
|
||||
entity:SetSubMaterial(plugin.config.patchIndex, pMat)
|
||||
else
|
||||
entity:SetSubMaterial(plugin.config.patchIndex, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local viewButtons = vgui.Create("DPanel", frame)
|
||||
viewButtons:SetPos(20, 530)
|
||||
viewButtons:SetSize(400, 50)
|
||||
viewButtons.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local bw = 125
|
||||
local bs = 12
|
||||
|
||||
local frontBtn = vgui.Create("DButton", viewButtons)
|
||||
frontBtn:SetPos(10, 10)
|
||||
frontBtn:SetSize(bw, 30)
|
||||
frontBtn:SetText("")
|
||||
frontBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_PRI_H or C_PRI
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText("СПЕРЕДИ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
frontBtn.DoClick = function() SetCameraAngle("front") end
|
||||
|
||||
local sideBtn = vgui.Create("DButton", viewButtons)
|
||||
sideBtn:SetPos(10 + bw + bs, 10)
|
||||
sideBtn:SetSize(bw, 30)
|
||||
sideBtn:SetText("")
|
||||
sideBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_PRI_H or C_PRI
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText("СБОКУ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
sideBtn.DoClick = function() SetCameraAngle("side") end
|
||||
|
||||
local backBtn = vgui.Create("DButton", viewButtons)
|
||||
backBtn:SetPos(10 + (bw + bs) * 2, 10)
|
||||
backBtn:SetSize(bw, 30)
|
||||
backBtn:SetText("")
|
||||
backBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_PRI_H or C_PRI
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
draw.SimpleText("СЗАДИ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
backBtn.DoClick = function() SetCameraAngle("back") end
|
||||
|
||||
local settingsPanel = vgui.Create("DPanel", frame)
|
||||
settingsPanel:SetPos(440, 60)
|
||||
settingsPanel:SetSize(440, 520)
|
||||
settingsPanel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local settingsScroll = vgui.Create("DScrollPanel", settingsPanel)
|
||||
settingsScroll:Dock(FILL)
|
||||
settingsScroll:DockMargin(10, 10, 10, 140)
|
||||
|
||||
local sbar = settingsScroll:GetVBar()
|
||||
sbar:SetWide(8)
|
||||
sbar.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) end
|
||||
sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_ACC) end
|
||||
sbar.btnUp.Paint = function() end
|
||||
sbar.btnDown.Paint = function() end
|
||||
|
||||
local yPos = 5
|
||||
|
||||
if plugin and plugin.config.patchMaterials and #plugin.config.patchMaterials > 0 then
|
||||
local patchLabel = vgui.Create("DPanel", settingsScroll)
|
||||
patchLabel:SetPos(10, yPos)
|
||||
patchLabel:SetSize(400, 35)
|
||||
patchLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
|
||||
surface.SetDrawColor(C_ACC)
|
||||
surface.DrawRect(0, 0, 3, h)
|
||||
draw.SimpleText("ПАТЧИ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
yPos = yPos + 40
|
||||
|
||||
local pH = vgui.Create("DPanel", settingsScroll)
|
||||
pH:SetPos(10, yPos)
|
||||
pH:SetSize(400, 65)
|
||||
pH.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_BG_L or C_BG_D
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local pS = vgui.Create("DNumSlider", pH)
|
||||
pS:SetPos(5, 15)
|
||||
pS:SetSize(390, 40)
|
||||
pS:SetMin(1)
|
||||
pS:SetMax(#plugin.config.patchMaterials)
|
||||
pS:SetDecimals(0)
|
||||
pS:SetValue(currentPatch)
|
||||
pS:SetText("Тип патча")
|
||||
pS.Slider.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
|
||||
local maxV = math.max(1, pS:GetMax() - pS:GetMin())
|
||||
local frac = (pS:GetValue() - pS:GetMin()) / maxV
|
||||
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
|
||||
end
|
||||
pS.Slider.Knob.Paint = function(s, w, h)
|
||||
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
|
||||
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
|
||||
end
|
||||
pS.OnValueChanged = function(s, value) currentPatch = math.floor(value) end
|
||||
|
||||
yPos = yPos + 70
|
||||
end
|
||||
|
||||
if table.Count(bodygroups) > 0 then
|
||||
local bgLabel = vgui.Create("DPanel", settingsScroll)
|
||||
bgLabel:SetPos(10, yPos)
|
||||
bgLabel:SetSize(400, 35)
|
||||
bgLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
|
||||
surface.SetDrawColor(C_ACC)
|
||||
surface.DrawRect(0, 0, 3, h)
|
||||
draw.SimpleText("ЧАСТИ ТЕЛА", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
yPos = yPos + 40
|
||||
|
||||
for idx, data in SortedPairsByMemberValue(bodygroups, "index") do
|
||||
local bgPanel = vgui.Create("DPanel", settingsScroll)
|
||||
bgPanel:SetPos(10, yPos)
|
||||
bgPanel:SetSize(400, 65)
|
||||
bgPanel.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_BG_L or C_BG_D
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local label = vgui.Create("DLabel", bgPanel)
|
||||
label:SetPos(10, 8)
|
||||
label:SetSize(380, 20)
|
||||
label:SetFont("DermaDefault")
|
||||
label:SetText(data.name)
|
||||
label:SetTextColor(C_TXT_P)
|
||||
|
||||
local slider = vgui.Create("DNumSlider", bgPanel)
|
||||
slider:SetPos(5, 28)
|
||||
slider:SetSize(390, 30)
|
||||
slider:SetMin(0)
|
||||
slider:SetMax(data.count - 1)
|
||||
slider:SetDecimals(0)
|
||||
slider:SetValue(currentBodygroups[idx] or 0)
|
||||
slider:SetText("")
|
||||
slider.Slider.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
|
||||
local maxV = math.max(1, slider:GetMax() - slider:GetMin())
|
||||
local frac = (slider:GetValue() - slider:GetMin()) / maxV
|
||||
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
|
||||
end
|
||||
slider.Slider.Knob.Paint = function(s, w, h)
|
||||
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
|
||||
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
|
||||
end
|
||||
slider.OnValueChanged = function(s, value) currentBodygroups[idx] = math.floor(value) end
|
||||
|
||||
yPos = yPos + 70
|
||||
end
|
||||
end
|
||||
|
||||
if #availableSkins > 1 then
|
||||
local skinLabel = vgui.Create("DPanel", settingsScroll)
|
||||
skinLabel:SetPos(10, yPos)
|
||||
skinLabel:SetSize(400, 35)
|
||||
skinLabel.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
|
||||
surface.SetDrawColor(C_ACC)
|
||||
surface.DrawRect(0, 0, 3, h)
|
||||
draw.SimpleText("СКИНЫ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
yPos = yPos + 40
|
||||
|
||||
local skinPanel = vgui.Create("DPanel", settingsScroll)
|
||||
skinPanel:SetPos(10, yPos)
|
||||
skinPanel:SetSize(400, 65)
|
||||
skinPanel.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_BG_L or C_BG_D
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 1)
|
||||
end
|
||||
|
||||
local skinSlider = vgui.Create("DNumSlider", skinPanel)
|
||||
skinSlider:SetPos(5, 15)
|
||||
skinSlider:SetSize(390, 40)
|
||||
skinSlider:SetMin(0)
|
||||
skinSlider:SetMax(#availableSkins)
|
||||
skinSlider:SetDecimals(0)
|
||||
skinSlider:SetValue(currentSkin)
|
||||
skinSlider:SetText("Скин")
|
||||
skinSlider.Slider.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
|
||||
local maxV = math.max(1, skinSlider:GetMax() - skinSlider:GetMin())
|
||||
local frac = (skinSlider:GetValue() - skinSlider:GetMin()) / maxV
|
||||
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
|
||||
end
|
||||
skinSlider.Slider.Knob.Paint = function(s, w, h)
|
||||
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
|
||||
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
|
||||
end
|
||||
skinSlider.OnValueChanged = function(s, value) currentSkin = math.floor(value) end
|
||||
|
||||
yPos = yPos + 70
|
||||
end
|
||||
|
||||
local wipeBtn = vgui.Create("DButton", settingsPanel)
|
||||
wipeBtn:SetPos(10, 390)
|
||||
wipeBtn:SetSize(420, 50)
|
||||
wipeBtn:SetText("")
|
||||
wipeBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_WARN_H or C_WARN
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
draw.SimpleText("СБРОСИТЬ", "DermaLarge", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
wipeBtn.DoClick = function()
|
||||
net.Start("ixWardrobeApply")
|
||||
net.WriteBool(true)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
end
|
||||
|
||||
local applyBtn = vgui.Create("DButton", settingsPanel)
|
||||
applyBtn:SetPos(10, 450)
|
||||
applyBtn:SetSize(420, 60)
|
||||
applyBtn:SetText("")
|
||||
applyBtn.Paint = function(s, w, h)
|
||||
local col = s:IsHovered() and C_PRI_H or C_PRI
|
||||
draw.RoundedBox(6, 0, 0, w, h, col)
|
||||
surface.SetDrawColor(C_GRAD)
|
||||
surface.DrawRect(0, 0, w, h/2)
|
||||
surface.SetDrawColor(C_BDR)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
draw.SimpleText("ПРИМЕНИТЬ", "DermaLarge", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
applyBtn.DoClick = function()
|
||||
net.Start("ixWardrobeApply")
|
||||
net.WriteBool(false)
|
||||
net.WriteTable(currentBodygroups)
|
||||
net.WriteUInt(currentSkin, 8)
|
||||
net.WriteUInt(currentPatch, 8)
|
||||
net.SendToServer()
|
||||
frame:Close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
979
garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_config.lua
Normal file
979
garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_config.lua
Normal file
@@ -0,0 +1,979 @@
|
||||
-- проверка пениса
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PLUGIN.config = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
currencyName = "Очки снабжения",
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Начальное снабжение для фракций
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
startSupply = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[FACTION_RUSSIAN or 1] = 2000,
|
||||
[FACTION_UKRAINE or 2] = 2000,
|
||||
}, -- [FACTION_ID] = amount
|
||||
|
||||
-- Максиммальное снабжение
|
||||
maxSupply = 20000,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Минимум, при котором арсенал считается закрытым
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
minSupply = 0,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Cooldown for free weapons (supplyPrice == 0), per-player, seconds
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
freeWeaponCooldown = 3600, -- default 1 hour
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Currency symbols per faction (client uses this to display money symbol)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
currencySymbol = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[FACTION_RUSSIAN or 1] = "₽",
|
||||
[FACTION_UKRAINE or 2] = "$",
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
weapons = {
|
||||
["tfa_inss_wpn_ak12"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 11000,
|
||||
["supplyPrice"] = 220,
|
||||
["name"] = "AK-12"
|
||||
},
|
||||
["tacrp_mr96"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "MR-96"
|
||||
},
|
||||
["tacrp_ak_ak74u"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 3000,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "AKС-74У"
|
||||
},
|
||||
["tacrp_io_glock18"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "Glock 18C"
|
||||
},
|
||||
["tacrp_ks23"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "КС-23"
|
||||
},
|
||||
["tacrp_as50"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 20000,
|
||||
["supplyPrice"] = 1000,
|
||||
["name"] = "AS50"
|
||||
},
|
||||
["weapon_lvsrepair"] = {
|
||||
["category"] = "tool1",
|
||||
["moneyPrice"] = 100,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Сварка"
|
||||
},
|
||||
["tacrp_io_trg42"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 450,
|
||||
["name"] = "Sako TRG-42"
|
||||
},
|
||||
["tacrp_sd_dual_degala"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "Двойные Deagle"
|
||||
},
|
||||
["weapon_cigarette_camel"] = {
|
||||
["category"] = "tool1",
|
||||
["moneyPrice"] = 100,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Сигаретка"
|
||||
},
|
||||
["tacrp_io_sg550"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "SIG SG 550"
|
||||
},
|
||||
["tacrp_io_rpk"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 150,
|
||||
["name"] = "РПК-74"
|
||||
},
|
||||
["tacrp_uzi"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "UZI"
|
||||
},
|
||||
["parachute_swep"] = {
|
||||
["category"] = "tool4",
|
||||
["moneyPrice"] = 150,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Парашют"
|
||||
},
|
||||
["tacrp_hk417"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "HK 417"
|
||||
},
|
||||
["tacrp_sd_bizon"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "ПП-19 Бизон"
|
||||
},
|
||||
["weapon_sw_at4"] = {
|
||||
["moneyPrice"] = 4500,
|
||||
["supplyPrice"] = 450,
|
||||
["maxCount"] = 1,
|
||||
["ammoType"] = "rpg_round",
|
||||
["category"] = "heavy",
|
||||
["maxAmmo"] = 1,
|
||||
["name"] = "AT4"
|
||||
},
|
||||
["fas2_ifak"] = {
|
||||
["category"] = "tool7",
|
||||
["moneyPrice"] = 500,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "Аптечка"
|
||||
},
|
||||
["bandage"] = {
|
||||
["category"] = "tool7",
|
||||
["moneyPrice"] = 500,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "Бинт"
|
||||
},
|
||||
["tacrp_io_saiga"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "Сайга-12К"
|
||||
},
|
||||
["tacrp_nade_frag"] = {
|
||||
["category"] = "grenade",
|
||||
["moneyPrice"] = 300,
|
||||
["supplyPrice"] = 30,
|
||||
["name"] = "Осколочная граната"
|
||||
},
|
||||
["tacrp_g36k"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "G36K"
|
||||
},
|
||||
["tfa_inss_wpn_l1a1"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "L1A1"
|
||||
},
|
||||
["bodycam_tablet"] = {
|
||||
["category"] = "tool3",
|
||||
["moneyPrice"] = 500,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "Боди-камера"
|
||||
},
|
||||
["gmod_camera"] = {
|
||||
["category"] = "tool3",
|
||||
["moneyPrice"] = 50,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Камера"
|
||||
},
|
||||
["admin_defib"] = {
|
||||
["category"] = "tool7",
|
||||
["moneyPrice"] = 8000,
|
||||
["supplyPrice"] = 5000,
|
||||
["name"] = "Дефибриллятор"
|
||||
},
|
||||
["tacrp_sg551"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 20000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "SG 551"
|
||||
},
|
||||
["tacrp_knife"] = {
|
||||
["category"] = "melee",
|
||||
["moneyPrice"] = 50,
|
||||
["supplyPrice"] = 0,
|
||||
["name"] = "Нож"
|
||||
},
|
||||
["guitar"] = {
|
||||
["category"] = "tool3",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Гитара"
|
||||
},
|
||||
["v92_bf2_medikit"] = {
|
||||
["category"] = "tool9",
|
||||
["moneyPrice"] = 1500,
|
||||
["supplyPrice"] = 150,
|
||||
["name"] = "Аптечка"
|
||||
},
|
||||
["weapon_sw_9k38"] = {
|
||||
["category"] = "heavy",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 500,
|
||||
["name"] = "9K38"
|
||||
},
|
||||
["tacrp_mp7"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "MP7"
|
||||
},
|
||||
["weapon_lvsspikestrip"] = {
|
||||
["category"] = "tool2",
|
||||
["moneyPrice"] = 100,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Шипы"
|
||||
},
|
||||
["tacrp_p250"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "P250"
|
||||
},
|
||||
["tacrp_m4"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "HK416"
|
||||
},
|
||||
["tfa_inss_wpn_m16a4"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "M16A4"
|
||||
},
|
||||
["tacrp_ak_svd"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 450,
|
||||
["name"] = "СВД"
|
||||
},
|
||||
["tacrp_io_xm8lmg"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "ХМ8 LMG"
|
||||
},
|
||||
["tacrp_mg4"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "HK MG4"
|
||||
},
|
||||
["tacrp_io_degala"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "Desert Eagle"
|
||||
},
|
||||
["tacrp_mp5"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "MP5A3"
|
||||
},
|
||||
["special_bandage"] = {
|
||||
["category"] = "tool8",
|
||||
["moneyPrice"] = 1000,
|
||||
["supplyPrice"] = 500,
|
||||
["name"] = "Шина от переломов"
|
||||
},
|
||||
["tacrp_ak_aek971"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "АЕК-971"
|
||||
},
|
||||
["tacrp_ak_ak12"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 11000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "АК-12"
|
||||
},
|
||||
["weapon_sw_rpg26"] = {
|
||||
["moneyPrice"] = 4500,
|
||||
["supplyPrice"] = 450,
|
||||
["maxCount"] = 1,
|
||||
["ammoType"] = "rpg_round",
|
||||
["category"] = "heavy",
|
||||
["maxAmmo"] = 1,
|
||||
["name"] = "РПГ-26"
|
||||
},
|
||||
["tacrp_spr"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "M700"
|
||||
},
|
||||
["tacrp_ex_m4a1"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "М4А1"
|
||||
},
|
||||
["tfa_new_inss_mk18"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "MK18"
|
||||
},
|
||||
["tacrp_io_fiveseven"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "FiveSeven"
|
||||
},
|
||||
["engineertoolmines"] = {
|
||||
["category"] = "tool5",
|
||||
["moneyPrice"] = 50000,
|
||||
["supplyPrice"] = 1000,
|
||||
["name"] = "Мины"
|
||||
},
|
||||
["tacrp_pdw"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 3000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "KAC PDW"
|
||||
},
|
||||
["tacrp_ak_an94"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "АН-94"
|
||||
},
|
||||
["weapon_r_handcuffs"] = {
|
||||
["category"] = "tool8",
|
||||
["moneyPrice"] = 1000,
|
||||
["supplyPrice"] = 10,
|
||||
["name"] = "Наручники"
|
||||
},
|
||||
["tfa_inss_wpn_fn_fal"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "FN FAL"
|
||||
},
|
||||
["weapon_rope_knife"] = {
|
||||
["category"] = "tool6",
|
||||
["moneyPrice"] = 50000,
|
||||
["supplyPrice"] = 500,
|
||||
["name"] = "Крюк кошка"
|
||||
},
|
||||
["tacrp_io_sg550r"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "SG-550R"
|
||||
},
|
||||
["weapon_sw_fim92"] = {
|
||||
["category"] = "heavy",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 500,
|
||||
["name"] = "FIM-92"
|
||||
},
|
||||
["tacrp_sd_groza"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "ОЦ-14"
|
||||
},
|
||||
["tacrp_ifak"] = {
|
||||
["category"] = "tool7",
|
||||
["moneyPrice"] = 500,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "Аптечка IFAK"
|
||||
},
|
||||
["tacrp_aug"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 250,
|
||||
["name"] = "AUG"
|
||||
},
|
||||
["weapon_sw_panzerfaust3"] = {
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 500,
|
||||
["maxCount"] = 1,
|
||||
["ammoType"] = "PanzerFaust3 Rocket",
|
||||
["category"] = "heavy",
|
||||
["maxAmmo"] = 1,
|
||||
["name"] = "Панцерфауст 3"
|
||||
},
|
||||
["tacrp_io_scarh"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "FN SCAR"
|
||||
},
|
||||
["tacrp_io_xm8car"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 250,
|
||||
["name"] = "XM8"
|
||||
},
|
||||
["tacrp_pa_makarov"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "ПМ"
|
||||
},
|
||||
["tacrp_bekas"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "Bekas"
|
||||
},
|
||||
["weapon_cuff_elastic"] = {
|
||||
["category"] = "tool8",
|
||||
["moneyPrice"] = 300,
|
||||
["supplyPrice"] = 20,
|
||||
["name"] = "Наручники"
|
||||
},
|
||||
["tacrp_ex_hecate"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 17000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "PGM Hecate"
|
||||
},
|
||||
["tacrp_ex_glock"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 20,
|
||||
["name"] = "Glock"
|
||||
},
|
||||
["tfa_ins2_aks_r"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 11000,
|
||||
["supplyPrice"] = 220,
|
||||
["name"] = "AKS-74U"
|
||||
},
|
||||
["tacrp_ak_ak74"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 50,
|
||||
["name"] = "АК-74"
|
||||
},
|
||||
["tacrp_io_sl8"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 9000,
|
||||
["supplyPrice"] = 180,
|
||||
["name"] = "SL8"
|
||||
},
|
||||
["tfa_ins2_moe_akm"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 12000,
|
||||
["supplyPrice"] = 240,
|
||||
["name"] = "AKM MOE"
|
||||
},
|
||||
["tacrp_io_val"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "АС ВАЛ"
|
||||
},
|
||||
["tacrp_skorpion"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "VZ-61"
|
||||
},
|
||||
["swep_drone_grenade"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 1000,
|
||||
["name"] = "Дрон с гранатой"
|
||||
},
|
||||
["tacrp_sd_pkm"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "ПКМ"
|
||||
},
|
||||
["weapon_sw_rpg28"] = {
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 500,
|
||||
["maxCount"] = 1,
|
||||
["ammoType"] = "rpg_round",
|
||||
["category"] = "heavy",
|
||||
["maxAmmo"] = 1,
|
||||
["name"] = "РПГ-28"
|
||||
},
|
||||
["tacrp_nade_flashbang"] = {
|
||||
["category"] = "grenade1",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 20,
|
||||
["name"] = "Светошумовая граната"
|
||||
},
|
||||
["v92_bf2_ammokit"] = {
|
||||
["category"] = "tool3",
|
||||
["moneyPrice"] = 1500,
|
||||
["supplyPrice"] = 150,
|
||||
["name"] = "Ящик с патронами"
|
||||
},
|
||||
["tacrp_io_vss"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "ВСС Винторез"
|
||||
},
|
||||
["tacrp_io_p226"] = {
|
||||
["category"] = "secondary",
|
||||
["moneyPrice"] = 200,
|
||||
["supplyPrice"] = 1,
|
||||
["name"] = "P226"
|
||||
},
|
||||
["tfa_blast_ksvk_cqb"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 15000,
|
||||
["supplyPrice"] = 300,
|
||||
["name"] = "KSvK 12.7"
|
||||
},
|
||||
["tacrp_io_k98"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 5000,
|
||||
["supplyPrice"] = 100,
|
||||
["name"] = "KAR98K"
|
||||
},
|
||||
["tacrp_superv"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 9000,
|
||||
["supplyPrice"] = 180,
|
||||
["name"] = "KRISS Vector"
|
||||
},
|
||||
["tacrp_nade_smoke"] = {
|
||||
["category"] = "grenade2",
|
||||
["moneyPrice"] = 150,
|
||||
["supplyPrice"] = 15,
|
||||
["name"] = "Дымовая граната"
|
||||
},
|
||||
["tacrp_sd_aac_hb"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "AAC Honey badger"
|
||||
},
|
||||
["tacrp_pa_vykhlop"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 17000,
|
||||
["supplyPrice"] = 350,
|
||||
["name"] = "ВСК Выхлоп"
|
||||
},
|
||||
["tacrp_sd_g3"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 10000,
|
||||
["supplyPrice"] = 200,
|
||||
["name"] = "G3A3"
|
||||
},
|
||||
["tfa_ins2_warface_cheytac_m200"] = {
|
||||
["category"] = "primary",
|
||||
["moneyPrice"] = 17000,
|
||||
["supplyPrice"] = 340,
|
||||
["name"] = "Cheytac M200"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- ============================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- DONATE WEAPONS (donate_only = true означает что доступно ТОЛЬКО через донат)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Если donate_only НЕ указано или false, оружие может быть добавлено в spec/podr и будет платным
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- ============================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Патроны: тип -> {name, price, amount}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ammo = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- ["rifle"] = { name = "Патроны для винтовки", price = 10, amount = 30 },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- Броня (опционально)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
armor = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- amount: how much armor will be set on the player after purchase
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- supplyPrice: cost in faction supply points (keeps naming consistent with weapons)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
["armor_light"] = { name = "Легкая броня", supplyPrice = 50, moneyPrice = 0, amount = 25 },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
["armor_medium"] = { name = "Средняя броня", supplyPrice = 75, moneyPrice = 0, amount = 50 },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
["armor_heavy"] = { name = "Тяжелая броня", supplyPrice = 150, moneyPrice = 0, amount = 100 },
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wardrobeBlacklist = {},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
patchIndex = 15,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
patchMaterials = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"models/texture_vsu_1amb/path",
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"models/texture_vsu_1amb/path2",
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"models/texture_vsu_1amb/path3"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wardrobeAccess = {},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wardrobeStats = {},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wardrobeCHands = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
64
garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_plugin.lua
Normal file
64
garrysmod/gamemodes/militaryrp/plugins/arsenal/sh_plugin.lua
Normal file
@@ -0,0 +1,64 @@
|
||||
local PLUGIN = PLUGIN
|
||||
PLUGIN.name = "Arsenal"
|
||||
PLUGIN.author = "Refosel"
|
||||
PLUGIN.description = "Arsenal plugin (skeleton) — выдача оружия, патронов и управление очками снабжения."
|
||||
|
||||
ix.util.Include("sh_config.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
|
||||
|
||||
ix.command.Add("test_arsenal", {
|
||||
description = "Test arsenal weapon availability.",
|
||||
OnRun = function(self, client)
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return "Plugin not found" end
|
||||
|
||||
local char = client:GetCharacter()
|
||||
if not char then return "No character" end
|
||||
|
||||
client:ChatPrint("--- Arsenal Test for " .. client:Nick() .. " ---")
|
||||
local avail = plugin:GetAvailableWeapons(char)
|
||||
client:ChatPrint("Available weapons total: " .. table.Count(avail))
|
||||
for class, data in pairs(avail) do
|
||||
client:ChatPrint(string.format(" [%s] %s (Donate: %s)", class, data.name or "N/A", tostring(data.isDonateVersion or false)))
|
||||
end
|
||||
client:ChatPrint("--- End Test ---")
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("arsenal_supply_set", {
|
||||
description = "Set faction supply. Usage: arsenal_supply_set <amount> [faction id]",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.number,
|
||||
ix.type.number
|
||||
},
|
||||
OnRun = function(self, client, amount, faction)
|
||||
if CLIENT then return end
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return client:Notify("Arsenal plugin not loaded") end
|
||||
|
||||
local fid = faction or client:GetCharacter():GetFaction()
|
||||
plugin:SetFactionSupply(fid, math.max(tonumber(amount) or 0, 0))
|
||||
client:Notify("Set supply for faction " .. tostring(fid) .. " to " .. tostring(plugin:GetFactionSupply(fid)))
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("arsenal_supply_add", {
|
||||
description = "Add (or subtract) supply for a faction. Usage: arsenal_supply_add <delta> [faction id]",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.number,
|
||||
ix.type.number
|
||||
},
|
||||
OnRun = function(self, client, delta, faction)
|
||||
if CLIENT then return end
|
||||
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
|
||||
if not plugin then return client:Notify("Arsenal plugin not loaded") end
|
||||
|
||||
local fid = faction or client:GetCharacter():GetFaction()
|
||||
plugin:AddFactionSupply(fid, tonumber(delta) or 0)
|
||||
client:Notify("Faction " .. tostring(fid) .. " supply now: " .. tostring(plugin:GetFactionSupply(fid)))
|
||||
end
|
||||
})
|
||||
751
garrysmod/gamemodes/militaryrp/plugins/arsenal/sv_plugin.lua
Normal file
751
garrysmod/gamemodes/militaryrp/plugins/arsenal/sv_plugin.lua
Normal file
@@ -0,0 +1,751 @@
|
||||
if SERVER then
|
||||
util.AddNetworkString("ixArsenalOpen")
|
||||
util.AddNetworkString("ixArsenalAction")
|
||||
util.AddNetworkString("ixWardrobeOpen")
|
||||
util.AddNetworkString("ixWardrobeApply")
|
||||
end
|
||||
|
||||
local PLUGIN = PLUGIN
|
||||
local config = PLUGIN.config
|
||||
|
||||
|
||||
|
||||
local function IsRecruit(ply)
|
||||
local char = ply:GetCharacter()
|
||||
if not char then return false end
|
||||
local spec = char.GetSpec and char:GetSpec() or 0
|
||||
return spec == 1
|
||||
end
|
||||
|
||||
-- Helpers to store complex data as JSON strings because SetData/GetData may not accept tables
|
||||
local function _loadJSONData(plugin, key)
|
||||
-- plugin:GetData() returns the whole plugin store (or default table). We keep subkeys inside that store.
|
||||
local store = plugin:GetData() or {}
|
||||
if type(store) ~= "table" then return {} end
|
||||
local raw = store[key]
|
||||
if raw == nil then return {} end
|
||||
if type(raw) == "table" then return raw end
|
||||
if type(raw) == "string" then
|
||||
local ok, tbl = pcall(util.JSONToTable, raw)
|
||||
if ok and istable(tbl) then return tbl end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
local function _saveJSONData(plugin, key, tbl)
|
||||
if type(tbl) ~= "table" then tbl = {} end
|
||||
local store = plugin:GetData() or {}
|
||||
if type(store) ~= "table" then store = {} end
|
||||
-- store the table directly under the subkey; ix.data.Set will serialize the whole store
|
||||
store[key] = tbl
|
||||
plugin:SetData(store)
|
||||
end
|
||||
|
||||
-- Faction supply storage helpers
|
||||
function PLUGIN:GetFactionSupply(faction)
|
||||
local store = _loadJSONData(self, "factionSupply") or {}
|
||||
local key = tostring(faction)
|
||||
local val = store[faction]
|
||||
if val == nil then val = store[key] end
|
||||
if val == nil then
|
||||
-- fallback to configured startSupply or 0
|
||||
return config.startSupply[faction] or 0
|
||||
end
|
||||
local maxSupply = (self.config and self.config.maxSupply) or 20000
|
||||
return math.Clamp(tonumber(val) or 0, 0, maxSupply)
|
||||
end
|
||||
|
||||
-- Returns remaining cooldown seconds for free weapons for a specific player (per-player persistent)
|
||||
function PLUGIN:GetFreeWeaponCooldownForPlayer(client)
|
||||
if not IsValid(client) or not client:IsPlayer() then return 0 end
|
||||
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
|
||||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||||
local last = tonumber(stamps[steam]) or 0
|
||||
local now = os.time()
|
||||
local cd = tonumber(config.freeWeaponCooldown) or 0
|
||||
if cd <= 0 then return 0 end
|
||||
local remain = cd - (now - last)
|
||||
if remain < 0 then remain = 0 end
|
||||
return math.floor(remain)
|
||||
end
|
||||
|
||||
-- Returns remaining cooldown seconds for donate weapons (10 minutes = 600 seconds)
|
||||
function PLUGIN:GetDonateWeaponCooldown(client, weaponClass)
|
||||
if not IsValid(client) or not client:IsPlayer() then return 0 end
|
||||
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
|
||||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||||
if not stamps[steam] then return 0 end
|
||||
local last = tonumber(stamps[steam][weaponClass]) or 0
|
||||
local now = os.time()
|
||||
local cd = 600 -- 10 минут
|
||||
local remain = cd - (now - last)
|
||||
if remain < 0 then remain = 0 end
|
||||
return math.floor(remain)
|
||||
end
|
||||
|
||||
function PLUGIN:SetDonateWeaponCooldown(client, weaponClass)
|
||||
if not IsValid(client) or not client:IsPlayer() then return end
|
||||
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
|
||||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||||
if not stamps[steam] then stamps[steam] = {} end
|
||||
stamps[steam][weaponClass] = os.time()
|
||||
_saveJSONData(self, "donateWeaponTimestamps", stamps)
|
||||
end
|
||||
|
||||
function PLUGIN:SetFactionSupply(faction, amount)
|
||||
local store = _loadJSONData(self, "factionSupply") or {}
|
||||
local key = tostring(faction)
|
||||
local value = math.max(tonumber(amount) or 0, 0)
|
||||
store[key] = value
|
||||
store[faction] = value
|
||||
_saveJSONData(self, "factionSupply", store)
|
||||
end
|
||||
|
||||
-- Compatibility helper: add (or subtract) supply for a faction
|
||||
function PLUGIN:AddFactionSupply(faction, delta)
|
||||
local amount = tonumber(delta) or 0
|
||||
local cur = tonumber(self:GetFactionSupply(faction)) or 0
|
||||
local maxSupply = (self.config and self.config.maxSupply) or 20000
|
||||
local new = math.Clamp(cur + amount, 0, maxSupply)
|
||||
|
||||
if new == cur then return end -- Если ничего не изменилось (например, уперлись в лимит)
|
||||
|
||||
self:SetFactionSupply(faction, new)
|
||||
|
||||
-- Логирование изменения снабжения
|
||||
local serverlogsPlugin = ix.plugin.list["serverlogs"]
|
||||
if (serverlogsPlugin) then
|
||||
local factionName = ix.faction.Get(faction).name or tostring(faction)
|
||||
if amount > 0 then
|
||||
local message = string.format("Фракция '%s' получила +%d очков снабжения (итого: %d)", factionName, amount, new)
|
||||
serverlogsPlugin:AddLog("FACTION_SUPPLY_ADD", message, nil, {
|
||||
faction = faction,
|
||||
factionName = factionName,
|
||||
amount = amount,
|
||||
total = new
|
||||
})
|
||||
elseif amount < 0 then
|
||||
local message = string.format("Фракция '%s' потратила %d очков снабжения (итого: %d)", factionName, math.abs(amount), new)
|
||||
serverlogsPlugin:AddLog("FACTION_SUPPLY_USE", message, nil, {
|
||||
faction = faction,
|
||||
factionName = factionName,
|
||||
amount = math.abs(amount),
|
||||
total = new
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return new
|
||||
end
|
||||
|
||||
-- Получить список доступного оружия для персонажа (заглушка)
|
||||
-- Рекомендуется реализовать: читать FACTION.Spec и FACTION.Podr из ix.faction.Get(faction)
|
||||
function PLUGIN:GetAvailableWeapons(char)
|
||||
local weapons = {}
|
||||
local faction = char:GetFaction()
|
||||
local factionTable = ix.faction.Get(faction)
|
||||
if not factionTable then
|
||||
return weapons
|
||||
end
|
||||
|
||||
local allowed = {}
|
||||
local specIndex = tonumber(char:GetSpec()) or 1
|
||||
if factionTable.Spec and factionTable.Spec[specIndex] and istable(factionTable.Spec[specIndex].weapons) then
|
||||
for _, c in ipairs(factionTable.Spec[specIndex].weapons) do
|
||||
allowed[c] = true
|
||||
end
|
||||
end
|
||||
|
||||
local podrIndex = tonumber(char:GetPodr()) or 1
|
||||
if factionTable.Podr and factionTable.Podr[podrIndex] and istable(factionTable.Podr[podrIndex].preset) then
|
||||
for _, c in ipairs(factionTable.Podr[podrIndex].preset) do
|
||||
allowed[c] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- donate weapons from character data (with expiration check)
|
||||
local donateWeaponsTimed = char:GetData("donate_weapons_timed", {})
|
||||
local donateTable = {}
|
||||
if donateWeaponsTimed and istable(donateWeaponsTimed) then
|
||||
local now = os.time()
|
||||
for wepClass, wepData in pairs(donateWeaponsTimed) do
|
||||
if istable(wepData) and wepData.expires and tonumber(wepData.expires) > now then
|
||||
donateTable[wepClass] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Поддержка старой системы без срока (если есть)
|
||||
local donateList = char:GetData("donate_weapons", false)
|
||||
if donateList and istable(donateList) then
|
||||
for _, c in ipairs(donateList) do donateTable[c] = true end
|
||||
end
|
||||
|
||||
-- IGS integration: check if player has any weapons bought via IGS
|
||||
local client = char:GetPlayer()
|
||||
if IsValid(client) then
|
||||
if IGS then
|
||||
if isfunction(IGS.GetItems) then
|
||||
local items = IGS.GetItems()
|
||||
for _, ITEM in pairs(items) do
|
||||
local uid = (isfunction(ITEM.GetUID) and ITEM:GetUID()) or ITEM.uid
|
||||
if not uid then continue end
|
||||
|
||||
local has = false
|
||||
if isfunction(client.HasPurchase) then
|
||||
has = client:HasPurchase(uid)
|
||||
elseif isfunction(IGS.PlayerHasItem) then
|
||||
has = IGS.PlayerHasItem(client, uid)
|
||||
end
|
||||
|
||||
if has then
|
||||
-- Try all possible ways IGS stores the weapon class
|
||||
local weaponClass = nil
|
||||
local weaponCandidates = {
|
||||
(isfunction(ITEM.GetWeapon) and ITEM:GetWeapon()),
|
||||
(isfunction(ITEM.GetMeta) and ITEM:GetMeta("weapon")),
|
||||
ITEM.weapon,
|
||||
ITEM.weapon_class,
|
||||
ITEM.class,
|
||||
ITEM.uniqueID,
|
||||
ITEM.val,
|
||||
uid
|
||||
}
|
||||
for _, candidate in ipairs(weaponCandidates) do
|
||||
if candidate and candidate ~= "" then
|
||||
weaponClass = candidate
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if weaponClass and type(weaponClass) ~= "string" then
|
||||
weaponClass = tostring(weaponClass)
|
||||
end
|
||||
|
||||
if not weaponClass then
|
||||
weaponClass = uid
|
||||
end
|
||||
|
||||
if weaponClass then
|
||||
-- Check if this class or a similar one exists in config
|
||||
if config.weapons[weaponClass] then
|
||||
donateTable[weaponClass] = true
|
||||
else
|
||||
for configClass, _ in pairs(config.weapons) do
|
||||
if configClass:find(weaponClass, 1, true) then
|
||||
donateTable[configClass] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Build result from config only if class is allowed and exists in config
|
||||
local supply = tonumber(self:GetFactionSupply(faction)) or 0
|
||||
for class, _ in pairs(allowed) do
|
||||
local data = config.weapons[class]
|
||||
if not data then continue end -- skip if not configured
|
||||
|
||||
-- Проверяем есть ли оружие в донате у игрока
|
||||
local hasDonate = donateTable[class]
|
||||
|
||||
if hasDonate then
|
||||
-- Если куплено в донате - показываем как донатное (бесплатно, с кулдауном)
|
||||
local donateData = table.Copy(data)
|
||||
donateData.isDonateVersion = true -- флаг что это донатная версия
|
||||
donateData.supplyPrice = 0
|
||||
donateData.moneyPrice = 0
|
||||
weapons[class] = donateData
|
||||
elseif data.donate_only then
|
||||
-- Оружие только для доната, но не куплено - не показываем
|
||||
continue
|
||||
else
|
||||
-- Обычное оружие из spec/podr - показываем с ценой из конфига
|
||||
if supply <= 0 then
|
||||
if (data.supplyPrice or 0) == 0 then weapons[class] = data end
|
||||
else
|
||||
weapons[class] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Добавляем оружие которое есть ТОЛЬКО в донате (не в spec/podr)
|
||||
for wepClass, _ in pairs(donateTable) do
|
||||
if not allowed[wepClass] then
|
||||
local data = config.weapons[wepClass]
|
||||
if data then
|
||||
local donateData = table.Copy(data)
|
||||
donateData.isDonateVersion = true
|
||||
donateData.supplyPrice = 0
|
||||
donateData.moneyPrice = 0
|
||||
weapons[wepClass] = donateData
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return weapons
|
||||
end
|
||||
|
||||
-- Заглушки для действий (реализуйте логику по необходимости)
|
||||
function PLUGIN:BuyWeapon(client, weaponClass, payMethod)
|
||||
local char = client:GetCharacter()
|
||||
if not char then return false, "Нет персонажа" end
|
||||
local available = self:GetAvailableWeapons(char)
|
||||
local data = available[weaponClass]
|
||||
if not data then return false, "Оружие недоступно для покупки" end
|
||||
payMethod = payMethod or "supply"
|
||||
local faction = char:GetFaction()
|
||||
|
||||
-- Option A: pay by money (Helix)
|
||||
if payMethod == "money" then
|
||||
local price = data.moneyPrice or 0
|
||||
if price == 0 then return false, "Это оружие нельзя купить за деньги" end
|
||||
if not char.HasMoney or not char:HasMoney(price) then return false, "Недостаточно денег" end
|
||||
char:TakeMoney(price)
|
||||
client:Give(weaponClass)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check if this is a donate weapon version
|
||||
local isDonateWeapon = (data.isDonateVersion == true)
|
||||
|
||||
if isDonateWeapon then
|
||||
-- Проверяем что оружие уже есть у игрока
|
||||
if client:HasWeapon(weaponClass) then
|
||||
return false, "У вас уже есть это оружие"
|
||||
end
|
||||
|
||||
-- Проверяем кулдаун для донат-оружия (10 минут)
|
||||
local cooldown = self:GetDonateWeaponCooldown(client, weaponClass)
|
||||
if cooldown > 0 then
|
||||
local minutes = math.floor(cooldown / 60)
|
||||
local seconds = cooldown % 60
|
||||
return false, string.format("Вы недавно брали это оружие. Подождите %02d:%02d", minutes, seconds)
|
||||
end
|
||||
|
||||
-- Проверяем категорию (донат оружие тоже может иметь категорию)
|
||||
local newCat = data.category or ""
|
||||
if newCat ~= "" and not newCat:find("^tool") then
|
||||
for _, wep in ipairs(client:GetWeapons()) do
|
||||
if not IsValid(wep) then continue end
|
||||
local wclass = wep:GetClass()
|
||||
local wdata = config.weapons[wclass]
|
||||
if wdata and wdata.category == newCat then
|
||||
return false, "Вы уже имеете оружие этой категории: " .. newCat
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check max ammo limit for launchers
|
||||
if data.maxAmmo then
|
||||
local totalAmmo = 0
|
||||
if data.ammoType == "PanzerFaust3 Rocket" or data.ammoType == "rpg_round" then
|
||||
totalAmmo = client:GetAmmoCount("rpg_round") + client:GetAmmoCount("PanzerFaust3 Rocket")
|
||||
else
|
||||
totalAmmo = client:GetAmmoCount(data.ammoType or "rpg_round")
|
||||
end
|
||||
if totalAmmo >= data.maxAmmo then
|
||||
return false, "Вы достигли максимального количества снарядов для этого типа оружия (" .. tostring(data.maxAmmo) .. ")"
|
||||
end
|
||||
end
|
||||
|
||||
-- Выдаем оружие и ставим кулдаун
|
||||
client:Give(weaponClass)
|
||||
|
||||
-- Reset ammo for launchers to prevent extra ammo
|
||||
if data.ammoType then
|
||||
client:SetAmmo(0, data.ammoType)
|
||||
end
|
||||
|
||||
self:SetDonateWeaponCooldown(client, weaponClass)
|
||||
|
||||
-- Логирование выдачи донат-оружия
|
||||
end
|
||||
|
||||
-- Default: pay by supply
|
||||
local supplyCost = data.supplyPrice or 0
|
||||
local factionSupply = self:GetFactionSupply(faction)
|
||||
|
||||
-- If weapon is free (supplyCost == 0), enforce per-player persistent cooldown
|
||||
if supplyCost == 0 then
|
||||
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
|
||||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||||
local last = tonumber(stamps[steam]) or 0
|
||||
local now = os.time()
|
||||
local cd = tonumber(config.freeWeaponCooldown) or 0
|
||||
if cd > 0 and (now - last) < cd then
|
||||
local remain = cd - (now - last)
|
||||
return false, "Вы уже брали бесплатное оружие недавно. Подождите " .. tostring(remain) .. " сек."
|
||||
end
|
||||
-- don't write timestamp yet: write after successful give
|
||||
else
|
||||
if supplyCost > 0 then
|
||||
if factionSupply < supplyCost then return false, "Недостаточно очков снабжения у фракции" end
|
||||
self:AddFactionSupply(faction, -supplyCost)
|
||||
end
|
||||
end
|
||||
-- Check category conflict: prevent two weapons from same category
|
||||
local newCat = data.category or ""
|
||||
if newCat ~= "" and not newCat:find("^tool") then
|
||||
for _, wep in ipairs(client:GetWeapons()) do
|
||||
if not IsValid(wep) then continue end
|
||||
local wclass = wep:GetClass()
|
||||
local wdata = config.weapons[wclass]
|
||||
if wdata and wdata.category == newCat then
|
||||
return false, "Вы уже имеете оружие этой категории: " .. newCat
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check max ammo limit for launchers
|
||||
if data.maxAmmo then
|
||||
local totalAmmo = client:GetAmmoCount(data.ammoType or "rpg_round")
|
||||
if totalAmmo >= data.maxAmmo then
|
||||
return false, "Вы достигли максимального количества снарядов для этого типа оружия (" .. tostring(data.maxAmmo) .. ")"
|
||||
end
|
||||
end
|
||||
-- Check max count limit for weapons
|
||||
if data.maxCount and client:HasWeapon(weaponClass) then
|
||||
return false, "У вас уже есть это оружие"
|
||||
end
|
||||
|
||||
client:Give(weaponClass)
|
||||
|
||||
-- Reset ammo for launchers to prevent extra ammo
|
||||
if data.ammoType then
|
||||
client:SetAmmo(0, data.ammoType)
|
||||
end
|
||||
-- If free weapon granted, save timestamp per-player
|
||||
if supplyCost == 0 then
|
||||
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
|
||||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||||
stamps[steam] = os.time()
|
||||
_saveJSONData(self, "freeWeaponTimestamps", stamps)
|
||||
end
|
||||
|
||||
-- Логирование выдачи оружия из арсенала
|
||||
local serverlogsPlugin = ix.plugin.list["serverlogs"]
|
||||
if (serverlogsPlugin) then
|
||||
local weaponName = data.name or weaponClass
|
||||
local factionName = ix.faction.Get(faction).name or tostring(faction)
|
||||
local message = string.format("%s получил оружие '%s' из арсенала (стоимость: %d снабжения)", client:Nick(), weaponName, supplyCost)
|
||||
serverlogsPlugin:AddLog("WEAPON_SPAWN", message, client, {
|
||||
weaponClass = weaponClass,
|
||||
weaponName = weaponName,
|
||||
supplyCost = supplyCost,
|
||||
faction = faction,
|
||||
factionName = factionName
|
||||
})
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function PLUGIN:BuyAmmo(client, ammoType)
|
||||
local char = client:GetCharacter()
|
||||
if not char then return false, "Нет персонажа" end
|
||||
|
||||
-- Запрет на покупку патронов для ракетниц
|
||||
if ammoType == "rpg_round" or ammoType == "PanzerFaust3 Rocket" then
|
||||
return false, "Патроны для этого типа оружия недоступны для покупки"
|
||||
end
|
||||
|
||||
local ammoData = config.ammo[ammoType]
|
||||
if not ammoData then return false, "Тип патронов не найден" end
|
||||
|
||||
local faction = char:GetFaction()
|
||||
local supply = self:GetFactionSupply(faction)
|
||||
|
||||
local price = ammoData.price or 0
|
||||
if price > 0 then
|
||||
if supply < price then return false, "Недостаточно очков снабжения у фракции" end
|
||||
self:AddFactionSupply(faction, -price)
|
||||
end
|
||||
|
||||
client:GiveAmmo(ammoData.amount or 30, ammoType, true)
|
||||
return true
|
||||
end
|
||||
|
||||
function PLUGIN:ReturnWeapon(client, weaponClass)
|
||||
local char = client:GetCharacter()
|
||||
if not char then return false, "Нет персонажа" end
|
||||
local data = config.weapons[weaponClass]
|
||||
if not data then return false, "Оружие не найдено" end
|
||||
|
||||
local faction = char:GetFaction()
|
||||
-- Remove weapon
|
||||
client:StripWeapon(weaponClass)
|
||||
|
||||
-- Reset ammo for launchers to prevent extra ammo
|
||||
if data.ammoType then
|
||||
client:SetAmmo(0, data.ammoType)
|
||||
end
|
||||
|
||||
-- Если это донат-оружие, сбрасываем кулдаун
|
||||
if data.donate == true then
|
||||
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
|
||||
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
|
||||
if stamps[steam] and stamps[steam][weaponClass] then
|
||||
stamps[steam][weaponClass] = nil
|
||||
_saveJSONData(self, "donateWeaponTimestamps", stamps)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local refund = math.floor((data.supplyPrice or 0) * 0.8)
|
||||
if refund > 0 then self:AddFactionSupply(faction, refund) end
|
||||
return true
|
||||
end
|
||||
|
||||
function PLUGIN:SaveData()
|
||||
local data = self:GetData() or {}
|
||||
data.entities = {}
|
||||
|
||||
for _, entity in ipairs(ents.FindByClass("ix_arsenal")) do
|
||||
data.entities[#data.entities + 1] = {
|
||||
class = "ix_arsenal",
|
||||
pos = entity:GetPos(),
|
||||
angles = entity:GetAngles()
|
||||
}
|
||||
end
|
||||
|
||||
for _, entity in ipairs(ents.FindByClass("ix_ammobox")) do
|
||||
data.entities[#data.entities + 1] = {
|
||||
class = "ix_ammobox",
|
||||
pos = entity:GetPos(),
|
||||
angles = entity:GetAngles()
|
||||
}
|
||||
end
|
||||
|
||||
for _, entity in ipairs(ents.FindByClass("ix_wardrobe")) do
|
||||
data.entities[#data.entities + 1] = {
|
||||
class = "ix_wardrobe",
|
||||
pos = entity:GetPos(),
|
||||
angles = entity:GetAngles()
|
||||
}
|
||||
end
|
||||
|
||||
self:SetData(data)
|
||||
end
|
||||
|
||||
function PLUGIN:LoadData()
|
||||
local data = self:GetData() or {}
|
||||
local entities = data.entities or data -- fallback to compatibility if no 'entities' key
|
||||
|
||||
for _, v in ipairs(entities) do
|
||||
if not v.class then continue end
|
||||
local entity = ents.Create(v.class or "ix_arsenal")
|
||||
entity:SetPos(v.pos)
|
||||
entity:SetAngles(v.angles)
|
||||
entity:Spawn()
|
||||
|
||||
local phys = entity:GetPhysicsObject()
|
||||
if IsValid(phys) then
|
||||
phys:EnableMotion(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Обработчик сетевых действий от клиента
|
||||
if SERVER then
|
||||
net.Receive("ixArsenalAction", function(len, ply)
|
||||
local action = net.ReadString()
|
||||
local id = net.ReadString()
|
||||
local plugin = ix.plugin.list["arsenal"]
|
||||
if not plugin then return end
|
||||
|
||||
if IsRecruit(ply) then
|
||||
ply:Notify("Новоприбывший не может пользоваться арсеналом.")
|
||||
return
|
||||
end
|
||||
|
||||
if action == "buy_weapon" then
|
||||
local method = net.ReadString()
|
||||
local ok, msg = plugin:BuyWeapon(ply, id, method)
|
||||
if not ok then
|
||||
ply:Notify(msg or "Ошибка покупки оружия")
|
||||
end
|
||||
elseif action == "buy_ammo" then
|
||||
local ok, msg = plugin:BuyAmmo(ply, id)
|
||||
if not ok then
|
||||
ply:Notify(msg or "Ошибка покупки патронов")
|
||||
end
|
||||
elseif action == "return_weapon" then
|
||||
local ok, msg = plugin:ReturnWeapon(ply, id)
|
||||
if not ok then
|
||||
ply:Notify(msg or "Ошибка возврата оружия")
|
||||
end
|
||||
elseif action == "buy_armor" then
|
||||
local method = net.ReadString()
|
||||
local ok, msg = plugin:BuyArmor(ply, id, method)
|
||||
if not ok then
|
||||
ply:Notify(msg or "Ошибка покупки брони")
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Buy armor implementation
|
||||
function PLUGIN:BuyArmor(client, armorClass, payMethod)
|
||||
local char = client:GetCharacter()
|
||||
if not char then return false, "Нет персонажа" end
|
||||
local aData = config.armor[armorClass]
|
||||
if not aData then return false, "Броня не найдена" end
|
||||
payMethod = payMethod or "supply"
|
||||
|
||||
-- Check current armor: Armor() is the player's current armor value
|
||||
local curArmor = tonumber(client:Armor()) or 0
|
||||
local giveAmount = tonumber(aData.amount) or 0
|
||||
if curArmor >= giveAmount then
|
||||
return false, "Вы не можете купить броню — у вас уже есть броня этого уровня или выше"
|
||||
end
|
||||
|
||||
local faction = char:GetFaction()
|
||||
|
||||
-- money path
|
||||
if payMethod == "money" then
|
||||
local price = aData.moneyPrice or 0
|
||||
if price <= 0 then return false, "Эту броню нельзя купить за деньги" end
|
||||
if not char.HasMoney or not char:HasMoney(price) then return false, "Недостаточно денег" end
|
||||
char:TakeMoney(price)
|
||||
client:SetArmor(giveAmount)
|
||||
return true
|
||||
end
|
||||
|
||||
-- supply path
|
||||
local supplyCost = aData.supplyPrice or 0
|
||||
local factionSupply = self:GetFactionSupply(faction)
|
||||
if supplyCost > 0 then
|
||||
if factionSupply < supplyCost then return false, "Недостаточно очков снабжения у фракции" end
|
||||
self:AddFactionSupply(faction, -supplyCost)
|
||||
end
|
||||
|
||||
client:SetArmor(giveAmount)
|
||||
return true
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerLoadedCharacter(client, character, currentChar)
|
||||
if not IsValid(client) then return end
|
||||
timer.Simple(0.5, function()
|
||||
if not IsValid(client) or not client:GetCharacter() then return end
|
||||
local char = client:GetCharacter()
|
||||
local bodygroups = char:GetData("bodygroups", {})
|
||||
local skin = char:GetData("skin", 0)
|
||||
local patch = char:GetData("patchIndex", 1)
|
||||
for idx, value in pairs(bodygroups) do
|
||||
client:SetBodygroup(idx, value)
|
||||
end
|
||||
client:SetSkin(skin)
|
||||
if self.config.patchIndex and self.config.patchMaterials then
|
||||
local pMat = self.config.patchMaterials[patch]
|
||||
if pMat then
|
||||
client:SetSubMaterial(self.config.patchIndex, pMat)
|
||||
else
|
||||
client:SetSubMaterial(self.config.patchIndex, "")
|
||||
end
|
||||
end
|
||||
hook.Run("PostPlayerLoadout", client)
|
||||
end)
|
||||
end
|
||||
|
||||
function PLUGIN:PostPlayerLoadout(client)
|
||||
local char = client:GetCharacter()
|
||||
if not char then return end
|
||||
local model = client:GetModel()
|
||||
local bgs = char:GetData("bodygroups", {})
|
||||
local speedMod = 1
|
||||
local maxArmor = 100
|
||||
local ammoMod = 1
|
||||
local stats = self.config.wardrobeStats and self.config.wardrobeStats[model]
|
||||
if stats then
|
||||
for idx, val in pairs(bgs) do
|
||||
if stats[idx] and stats[idx][val] then
|
||||
local st = stats[idx][val]
|
||||
if st.speed then speedMod = speedMod * st.speed end
|
||||
if st.armor then maxArmor = maxArmor + st.armor end
|
||||
if st.ammo then ammoMod = ammoMod * st.ammo end
|
||||
end
|
||||
end
|
||||
end
|
||||
client:SetWalkSpeed(ix.config.Get("walkSpeed") * speedMod)
|
||||
client:SetRunSpeed(ix.config.Get("runSpeed") * speedMod)
|
||||
client:SetMaxArmor(maxArmor)
|
||||
char:SetData("ammoMod", ammoMod)
|
||||
end
|
||||
|
||||
function PLUGIN:EntityTakeDamage(target, dmginfo)
|
||||
if not target:IsPlayer() then return end
|
||||
local char = target:GetCharacter()
|
||||
if not char then return end
|
||||
local model = target:GetModel()
|
||||
local bgs = char:GetData("bodygroups", {})
|
||||
local resist = 1
|
||||
local stats = self.config.wardrobeStats and self.config.wardrobeStats[model]
|
||||
if stats then
|
||||
for idx, val in pairs(bgs) do
|
||||
if stats[idx] and stats[idx][val] then
|
||||
local st = stats[idx][val]
|
||||
if st.dmgResist then
|
||||
resist = resist * st.dmgResist
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if resist ~= 1 then
|
||||
dmginfo:ScaleDamage(resist)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:InitPostEntity()
|
||||
-- Уменьшаем задержку до 2 секунд для более быстрой очистки при старте
|
||||
timer.Simple(2, function()
|
||||
-- Загружаем сырые данные из стора напрямую
|
||||
local currentData = _loadJSONData(self, "factionSupply") or {}
|
||||
|
||||
-- Перебираем все записи в сторе. Это надежнее, чем идти по индексам фракций,
|
||||
-- так как это затронет любые старые или "кривые" ключи в базе.
|
||||
local changed = false
|
||||
for key, value in pairs(currentData) do
|
||||
local factionID = tonumber(key)
|
||||
local rawVal = tonumber(value) or 0
|
||||
|
||||
if factionID and rawVal > 5000 then
|
||||
local startVal = (self.config.startSupply and self.config.startSupply[factionID]) or 2000
|
||||
|
||||
-- Устанавливаем новое значение
|
||||
currentData[key] = startVal
|
||||
currentData[factionID] = startVal
|
||||
changed = true
|
||||
|
||||
print(string.format("[Arsenal] Автосброс при старте: фракция %s, было %d, стало %d", key, rawVal, startVal))
|
||||
end
|
||||
end
|
||||
|
||||
if changed then
|
||||
_saveJSONData(self, "factionSupply", currentData)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerSetHandsModel(client, ent)
|
||||
local char = client:GetCharacter()
|
||||
if not char then return end
|
||||
local model = client:GetModel()
|
||||
local bgs = char:GetData("bodygroups", {})
|
||||
local chands = self.config.wardrobeCHands and self.config.wardrobeCHands[model]
|
||||
if chands then
|
||||
for idx, val in pairs(bgs) do
|
||||
if chands[idx] and chands[idx][val] then
|
||||
local hData = chands[idx][val]
|
||||
ent:SetModel(hData.model or "models/weapons/c_arms_cstrike.mdl")
|
||||
ent:SetSkin(hData.skin or 0)
|
||||
ent:SetBodyGroups(hData.bodygroups or "0000000")
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,113 @@
|
||||
function AdvDupe2.ReceiveFile(data, autoSave)
|
||||
AdvDupe2.RemoveProgressBar()
|
||||
if not data then
|
||||
AdvDupe2.Notify("File was not saved! (No data)",NOTIFY_ERROR,5)
|
||||
return
|
||||
end
|
||||
local path
|
||||
if autoSave then
|
||||
if(LocalPlayer():GetInfo("advdupe2_auto_save_overwrite")~="0")then
|
||||
path = AdvDupe2.GetFilename(AdvDupe2.AutoSavePath, true)
|
||||
else
|
||||
path = AdvDupe2.GetFilename(AdvDupe2.AutoSavePath)
|
||||
end
|
||||
else
|
||||
path = AdvDupe2.GetFilename(AdvDupe2.SavePath)
|
||||
end
|
||||
|
||||
path = AdvDupe2.SanitizeFilename(path)
|
||||
local dupefile = file.Open(path, "wb", "DATA")
|
||||
if not dupefile then
|
||||
AdvDupe2.Notify("File was not saved! (Could not open file for writing)",NOTIFY_ERROR,5)
|
||||
return
|
||||
end
|
||||
dupefile:Write(data)
|
||||
dupefile:Close()
|
||||
|
||||
local errored = false
|
||||
if(LocalPlayer():GetInfo("advdupe2_debug_openfile")=="1")then
|
||||
if(not file.Exists(path, "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end
|
||||
|
||||
local readFile = file.Open(path, "rb", "DATA")
|
||||
if not readFile then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end
|
||||
local readData = readFile:Read(readFile:Size())
|
||||
readFile:Close()
|
||||
local success, dupe, _info, _moreinfo = AdvDupe2.Decode(readData)
|
||||
if(success)then
|
||||
AdvDupe2.Notify("DEBUG CHECK: File successfully opens. No EOF errors.")
|
||||
else
|
||||
AdvDupe2.Notify("DEBUG CHECK: " .. dupe, NOTIFY_ERROR)
|
||||
errored = true
|
||||
end
|
||||
end
|
||||
|
||||
local filename = string.StripExtension(string.GetFileFromFilename( path ))
|
||||
if autoSave then
|
||||
if(IsValid(AdvDupe2.FileBrowser.AutoSaveNode))then
|
||||
local add = true
|
||||
for i=1, #AdvDupe2.FileBrowser.AutoSaveNode.Files do
|
||||
if(filename==AdvDupe2.FileBrowser.AutoSaveNode.Files[i].Label:GetText())then
|
||||
add=false
|
||||
break
|
||||
end
|
||||
end
|
||||
if(add)then
|
||||
AdvDupe2.FileBrowser.AutoSaveNode:AddFile(filename)
|
||||
AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.AutoSaveNode)
|
||||
end
|
||||
end
|
||||
else
|
||||
AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode:AddFile(filename)
|
||||
AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode)
|
||||
end
|
||||
if not errored then
|
||||
AdvDupe2.Notify("File successfully saved!",NOTIFY_GENERIC, 5)
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive("AdvDupe2_ReceiveFile", function()
|
||||
local autoSave = net.ReadBool()
|
||||
net.ReadStream(nil, function(data)
|
||||
AdvDupe2.ReceiveFile(data, autoSave)
|
||||
end)
|
||||
end)
|
||||
|
||||
AdvDupe2.Uploading = false
|
||||
function AdvDupe2.SendFile(name, data)
|
||||
net.Start("AdvDupe2_ReceiveFile")
|
||||
net.WriteString(name)
|
||||
AdvDupe2.Uploading = net.WriteStream(data, function()
|
||||
AdvDupe2.Uploading = nil
|
||||
AdvDupe2.File = nil
|
||||
AdvDupe2.RemoveProgressBar()
|
||||
end)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
function AdvDupe2.UploadFile(ReadPath, ReadArea)
|
||||
if AdvDupe2.Uploading then AdvDupe2.Notify("Already opening file, please wait.", NOTIFY_ERROR) return end
|
||||
if(ReadArea==0)then
|
||||
ReadPath = AdvDupe2.DataFolder.."/"..ReadPath..".txt"
|
||||
elseif(ReadArea==1)then
|
||||
ReadPath = AdvDupe2.DataFolder.."/-Public-/"..ReadPath..".txt"
|
||||
else
|
||||
ReadPath = "adv_duplicator/"..ReadPath..".txt"
|
||||
end
|
||||
|
||||
if(not file.Exists(ReadPath, "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end
|
||||
|
||||
local read = file.Read(ReadPath)
|
||||
if not read then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end
|
||||
local name = string.Explode("/", ReadPath)
|
||||
name = name[#name]
|
||||
name = string.sub(name, 1, #name-4)
|
||||
|
||||
local success, dupe, info, moreinfo = AdvDupe2.Decode(read)
|
||||
if(success)then
|
||||
AdvDupe2.SendFile(name, read)
|
||||
|
||||
AdvDupe2.LoadGhosts(dupe, info, moreinfo, name)
|
||||
else
|
||||
AdvDupe2.Notify("File could not be decoded. ("..dupe..") Upload Canceled.", NOTIFY_ERROR)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,353 @@
|
||||
function AdvDupe2.LoadGhosts(dupe, info, moreinfo, name, preview)
|
||||
AdvDupe2.RemoveGhosts()
|
||||
AdvDupe2.Ghosting = true
|
||||
AdvDupe2.GhostToSpawn = {}
|
||||
local count = 0
|
||||
local time, desc, date, creator
|
||||
|
||||
if(info.ad1) then
|
||||
local z = dupe.HeadEnt.Z
|
||||
local Pos, Ang
|
||||
|
||||
time = moreinfo.Time or ""
|
||||
desc = info.Description or ""
|
||||
date = info.Date or ""
|
||||
creator = info.Creator or ""
|
||||
|
||||
AdvDupe2.HeadEnt = dupe.HeadEnt.Index
|
||||
AdvDupe2.HeadPos = dupe.HeadEnt.Pos
|
||||
AdvDupe2.HeadZPos = z
|
||||
AdvDupe2.HeadPos.Z = AdvDupe2.HeadPos.Z + z
|
||||
|
||||
for k, v in pairs(dupe.Entities) do
|
||||
if(v.SavedParentIdx) then
|
||||
if(not v.BuildDupeInfo) then v.BuildDupeInfo = {} end
|
||||
v.BuildDupeInfo.DupeParentID = v.SavedParentIdx
|
||||
Pos = v.LocalPos
|
||||
Ang = v.LocalAngle
|
||||
else
|
||||
Pos, Ang = nil, nil
|
||||
end
|
||||
|
||||
for i, p in pairs(v.PhysicsObjects) do
|
||||
p.Pos = Pos or p.LocalPos
|
||||
p.Pos.Z = p.Pos.Z - z
|
||||
p.Angle = Ang or p.LocalAngle
|
||||
p.LocalPos = nil
|
||||
p.LocalAngle = nil
|
||||
end
|
||||
|
||||
v.LocalPos = nil
|
||||
v.LocalAngle = nil
|
||||
AdvDupe2.GhostToSpawn[count] =
|
||||
{
|
||||
Model = v.Model,
|
||||
PhysicsObjects = v.PhysicsObjects
|
||||
}
|
||||
|
||||
if(AdvDupe2.HeadEnt == k) then
|
||||
AdvDupe2.HeadEnt = count
|
||||
end
|
||||
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos
|
||||
AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle
|
||||
else
|
||||
time = info.time or ""
|
||||
desc = dupe.Description or ""
|
||||
date = info.date or ""
|
||||
creator = info.name or ""
|
||||
|
||||
AdvDupe2.HeadEnt = dupe.HeadEnt.Index
|
||||
AdvDupe2.HeadZPos = dupe.HeadEnt.Z
|
||||
AdvDupe2.HeadPos = dupe.HeadEnt.Pos
|
||||
AdvDupe2.HeadOffset = dupe.Entities[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos
|
||||
AdvDupe2.HeadAngle = dupe.Entities[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle
|
||||
|
||||
for k, v in pairs(dupe.Entities) do
|
||||
AdvDupe2.GhostToSpawn[count] =
|
||||
{
|
||||
Model = v.Model,
|
||||
PhysicsObjects = v.PhysicsObjects
|
||||
}
|
||||
|
||||
if(AdvDupe2.HeadEnt == k) then
|
||||
AdvDupe2.HeadEnt = count
|
||||
end
|
||||
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
if(not preview) then
|
||||
AdvDupe2.Info.File:SetText("File: "..name)
|
||||
AdvDupe2.Info.Creator:SetText("Creator: "..creator)
|
||||
AdvDupe2.Info.Date:SetText("Date: "..date)
|
||||
AdvDupe2.Info.Time:SetText("Time: "..time)
|
||||
AdvDupe2.Info.Size:SetText("Size: "..string.NiceSize(tonumber(info.size) or 0))
|
||||
AdvDupe2.Info.Desc:SetText("Desc: "..(desc or ""))
|
||||
AdvDupe2.Info.Entities:SetText("Entities: "..table.Count(dupe.Entities))
|
||||
AdvDupe2.Info.Constraints:SetText("Constraints: "..table.Count(dupe.Constraints))
|
||||
end
|
||||
|
||||
AdvDupe2.StartGhosting()
|
||||
AdvDupe2.Preview = preview
|
||||
end
|
||||
|
||||
function AdvDupe2.RemoveGhosts()
|
||||
if(AdvDupe2.Ghosting) then
|
||||
hook.Remove("Tick", "AdvDupe2_SpawnGhosts")
|
||||
AdvDupe2.Ghosting = false
|
||||
|
||||
if(not AdvDupe2.BusyBar) then
|
||||
AdvDupe2.RemoveProgressBar()
|
||||
end
|
||||
end
|
||||
|
||||
if(AdvDupe2.GhostEntities) then
|
||||
for k, v in pairs(AdvDupe2.GhostEntities) do
|
||||
if(IsValid(v))then
|
||||
v:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if(IsValid(AdvDupe2.HeadGhost))then
|
||||
AdvDupe2.HeadGhost:Remove()
|
||||
end
|
||||
|
||||
AdvDupe2.CurrentGhost = 1
|
||||
AdvDupe2.HeadGhost = nil
|
||||
AdvDupe2.GhostEntities = nil
|
||||
AdvDupe2.Preview = false
|
||||
end
|
||||
|
||||
--Creates a ghost from the given entity's table
|
||||
local function MakeGhostsFromTable(EntTable)
|
||||
|
||||
if(not EntTable) then return end
|
||||
if(not EntTable.Model or EntTable.Model:sub(-4,-1) ~= ".mdl") then
|
||||
EntTable.Model = "models/error.mdl"
|
||||
end
|
||||
|
||||
local GhostEntity = ClientsideModel(EntTable.Model, RENDERGROUP_TRANSLUCENT)
|
||||
|
||||
-- If there are too many entities we might not spawn..
|
||||
if not IsValid(GhostEntity) then
|
||||
AdvDupe2.RemoveGhosts()
|
||||
AdvDupe2.Notify("Too many entities to spawn ghosts!", NOTIFY_ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA ) --Was broken, making ghosts invisible
|
||||
GhostEntity:SetColor( Color(255, 255, 255, 150) )
|
||||
GhostEntity.Phys = EntTable.PhysicsObjects[0]
|
||||
|
||||
if util.IsValidRagdoll(EntTable.Model) then
|
||||
local ref, parents, angs = {}, {}, {}
|
||||
|
||||
GhostEntity:SetupBones()
|
||||
for k, v in pairs(EntTable.PhysicsObjects) do
|
||||
local bone = GhostEntity:TranslatePhysBoneToBone(k)
|
||||
local bonp = GhostEntity:GetBoneParent(bone)
|
||||
if bonp == -1 then
|
||||
ref[bone] = GhostEntity:GetBoneMatrix(bone):GetInverseTR()
|
||||
else
|
||||
bonp = GhostEntity:TranslatePhysBoneToBone(GhostEntity:TranslateBoneToPhysBone(bonp))
|
||||
parents[bone] = bonp
|
||||
ref[bone] = GhostEntity:GetBoneMatrix(bone):GetInverseTR() * GhostEntity:GetBoneMatrix(bonp)
|
||||
end
|
||||
|
||||
local m = Matrix() m:SetAngles(v.Angle)
|
||||
angs[bone] = m
|
||||
end
|
||||
|
||||
for bone, ang in pairs( angs ) do
|
||||
if parents[bone] and angs[parents[bone]] then
|
||||
local localrotation = angs[parents[bone]]:GetInverseTR() * ang
|
||||
local m = ref[bone] * localrotation
|
||||
GhostEntity:ManipulateBoneAngles(bone, m:GetAngles())
|
||||
else
|
||||
local pos = GhostEntity:GetBonePosition(bone)
|
||||
GhostEntity:ManipulateBonePosition(bone, -pos)
|
||||
GhostEntity:ManipulateBoneAngles(bone, ref[bone]:GetAngles())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return GhostEntity
|
||||
end
|
||||
|
||||
local function StopGhosting()
|
||||
AdvDupe2.Ghosting = false
|
||||
hook.Remove( "Tick", "AdvDupe2_SpawnGhosts" )
|
||||
|
||||
if not BusyBar then AdvDupe2.RemoveProgressBar() end
|
||||
end
|
||||
|
||||
local function SpawnGhosts()
|
||||
local ghostsPerTick = GetConVar( "advdupe2_ghost_rate" ):GetInt()
|
||||
local ghostPercentLimit = GetConVar( "advdupe2_limit_ghost" ):GetFloat()
|
||||
|
||||
local finalGhost = math.min( AdvDupe2.TotalGhosts, math.max( math.Round( (ghostPercentLimit / 100) * AdvDupe2.TotalGhosts ), 0 ) )
|
||||
local finalGhostInFrame = math.min( AdvDupe2.CurrentGhost + ghostsPerTick - 1, finalGhost )
|
||||
|
||||
for i = AdvDupe2.CurrentGhost, finalGhostInFrame do
|
||||
local g = AdvDupe2.GhostToSpawn[i]
|
||||
if g and i ~= AdvDupe2.HeadEnt then AdvDupe2.GhostEntities[i] = MakeGhostsFromTable( g ) end
|
||||
end
|
||||
AdvDupe2.CurrentGhost = finalGhostInFrame + 1
|
||||
|
||||
AdvDupe2.UpdateGhosts( true )
|
||||
if not AdvDupe2.BusyBar then
|
||||
AdvDupe2.ProgressBar.Percent = (AdvDupe2.CurrentGhost / AdvDupe2.TotalGhosts) * 100
|
||||
end
|
||||
|
||||
if AdvDupe2.CurrentGhost > finalGhost then
|
||||
StopGhosting()
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive("AdvDupe2_SendGhosts", function(len, ply, len2)
|
||||
AdvDupe2.RemoveGhosts()
|
||||
AdvDupe2.GhostToSpawn = {}
|
||||
AdvDupe2.HeadEnt = net.ReadInt(16)
|
||||
AdvDupe2.HeadZPos = net.ReadFloat()
|
||||
AdvDupe2.HeadPos = net.ReadVector()
|
||||
|
||||
local cache = {}
|
||||
for i = 1, net.ReadInt(16) do
|
||||
cache[i] = net.ReadString()
|
||||
end
|
||||
|
||||
for i = 1, net.ReadInt(16) do
|
||||
AdvDupe2.GhostToSpawn[i] =
|
||||
{
|
||||
Model = cache[net.ReadInt(16)],
|
||||
PhysicsObjects = {}
|
||||
}
|
||||
|
||||
for k = 0, net.ReadInt(8) do
|
||||
AdvDupe2.GhostToSpawn[i].PhysicsObjects[k] =
|
||||
{
|
||||
Angle = net.ReadAngle(),
|
||||
Pos = net.ReadVector()
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
AdvDupe2.CurrentGhost = 1
|
||||
AdvDupe2.GhostEntities = {}
|
||||
AdvDupe2.HeadGhost = MakeGhostsFromTable(AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt])
|
||||
AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos
|
||||
AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle
|
||||
AdvDupe2.GhostEntities[AdvDupe2.HeadEnt] = AdvDupe2.HeadGhost
|
||||
AdvDupe2.TotalGhosts = #AdvDupe2.GhostToSpawn
|
||||
|
||||
if(AdvDupe2.TotalGhosts > 1) then
|
||||
AdvDupe2.Ghosting = true
|
||||
|
||||
if(not AdvDupe2.BusyBar) then
|
||||
AdvDupe2.InitProgressBar("Ghosting: ")
|
||||
AdvDupe2.BusyBar = false
|
||||
end
|
||||
|
||||
hook.Add("Tick", "AdvDupe2_SpawnGhosts", SpawnGhosts)
|
||||
else
|
||||
AdvDupe2.Ghosting = false
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("AdvDupe2_AddGhost", function(len, ply, len2)
|
||||
local ghost = {Model = net.ReadString(), PhysicsObjects = {}}
|
||||
for k = 0, net.ReadInt(8) do
|
||||
ghost.PhysicsObjects[k] = {Angle = net.ReadAngle(), Pos = net.ReadVector()}
|
||||
end
|
||||
|
||||
AdvDupe2.GhostEntities[AdvDupe2.CurrentGhost] = MakeGhostsFromTable(ghost)
|
||||
AdvDupe2.CurrentGhost = AdvDupe2.CurrentGhost + 1
|
||||
end)
|
||||
|
||||
function AdvDupe2.StartGhosting()
|
||||
AdvDupe2.RemoveGhosts()
|
||||
if(not AdvDupe2.GhostToSpawn) then return end
|
||||
AdvDupe2.CurrentGhost = 1
|
||||
AdvDupe2.GhostEntities = {}
|
||||
AdvDupe2.Ghosting = true
|
||||
AdvDupe2.HeadGhost = MakeGhostsFromTable(AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt])
|
||||
AdvDupe2.GhostEntities[AdvDupe2.HeadEnt] = AdvDupe2.HeadGhost
|
||||
AdvDupe2.TotalGhosts = #AdvDupe2.GhostToSpawn
|
||||
|
||||
if AdvDupe2.TotalGhosts > 1 then
|
||||
if not AdvDupe2.BusyBar then
|
||||
AdvDupe2.InitProgressBar("Ghosting: ")
|
||||
AdvDupe2.BusyBar = false
|
||||
end
|
||||
hook.Add("Tick", "AdvDupe2_SpawnGhosts", SpawnGhosts)
|
||||
else
|
||||
AdvDupe2.Ghosting = false
|
||||
end
|
||||
end
|
||||
net.Receive("AdvDupe2_StartGhosting", function()
|
||||
AdvDupe2.StartGhosting()
|
||||
end)
|
||||
|
||||
net.Receive("AdvDupe2_RemoveGhosts", AdvDupe2.RemoveGhosts)
|
||||
|
||||
--Update the ghost's postion and angles based on where the player is looking and the offsets
|
||||
local Lheadpos, Lheadang = Vector(), Angle()
|
||||
function AdvDupe2.UpdateGhosts(force)
|
||||
if not IsValid(AdvDupe2.HeadGhost) then
|
||||
AdvDupe2.RemoveGhosts()
|
||||
AdvDupe2.Notify("Invalid ghost parent!", NOTIFY_ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local trace = LocalPlayer():GetEyeTrace()
|
||||
if (not trace.Hit) then return end
|
||||
|
||||
local originpos, originang, headpos, headang
|
||||
local worigin = GetConVar("advdupe2_offset_world"):GetBool()
|
||||
if(GetConVar("advdupe2_original_origin"):GetBool())then
|
||||
originang = Angle()
|
||||
originpos = Vector(AdvDupe2.HeadPos)
|
||||
headpos = AdvDupe2.HeadPos + AdvDupe2.HeadOffset
|
||||
headang = AdvDupe2.HeadAngle
|
||||
else
|
||||
local hangle = worigin and Angle(0,0,0) or AdvDupe2.HeadAngle
|
||||
local pz = math.Clamp(AdvDupe2.HeadZPos + GetConVar("advdupe2_offset_z"):GetFloat() or 0, -16000, 16000)
|
||||
local ap = math.Clamp(GetConVar("advdupe2_offset_pitch"):GetFloat() or 0, -180, 180)
|
||||
local ay = math.Clamp(GetConVar("advdupe2_offset_yaw" ):GetFloat() or 0, -180, 180)
|
||||
local ar = math.Clamp(GetConVar("advdupe2_offset_roll" ):GetFloat() or 0, -180, 180)
|
||||
originang = Angle(ap, ay, ar)
|
||||
originpos = Vector(trace.HitPos); originpos.z = originpos.z + pz
|
||||
headpos, headang = LocalToWorld(AdvDupe2.HeadOffset, hangle, originpos, originang)
|
||||
end
|
||||
|
||||
if math.abs(Lheadpos.x - headpos.x) > 0.01 or
|
||||
math.abs(Lheadpos.y - headpos.y) > 0.01 or
|
||||
math.abs(Lheadpos.z - headpos.z) > 0.01 or
|
||||
math.abs(Lheadang.p - headang.p) > 0.01 or
|
||||
math.abs(Lheadang.y - headang.y) > 0.01 or
|
||||
math.abs(Lheadang.r - headang.r) > 0.01 or force then
|
||||
|
||||
Lheadpos = headpos
|
||||
Lheadang = headang
|
||||
|
||||
AdvDupe2.HeadGhost:SetPos(headpos)
|
||||
AdvDupe2.HeadGhost:SetAngles(headang)
|
||||
|
||||
for k, ghost in ipairs(AdvDupe2.GhostEntities) do
|
||||
local phys = ghost.Phys
|
||||
|
||||
if phys then
|
||||
local pos, ang = LocalToWorld(phys.Pos, phys.Angle, originpos, originang)
|
||||
ghost:SetPos(pos)
|
||||
ghost:SetAngles(ang)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,564 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Codec
|
||||
|
||||
Desc: Dupe encoder/decoder.
|
||||
|
||||
Author: emspike
|
||||
|
||||
Version: 2.0
|
||||
]]
|
||||
|
||||
local REVISION = 5
|
||||
AdvDupe2.CodecRevision = REVISION
|
||||
AdvDupe2.MaxDupeSize = 32e6 -- 32 MB
|
||||
|
||||
include( "sh_codec_legacy.lua" )
|
||||
AddCSLuaFile( "sh_codec_legacy.lua" )
|
||||
|
||||
local pairs = pairs
|
||||
local error = error
|
||||
local Vector = Vector
|
||||
local Angle = Angle
|
||||
local format = string.format
|
||||
local char = string.char
|
||||
local concat = table.concat
|
||||
local compress = util.Compress
|
||||
local decompress = util.Decompress
|
||||
|
||||
--[[
|
||||
Name: GenerateDupeStamp
|
||||
Desc: Generates an info table.
|
||||
Params: <player> ply
|
||||
Return: <table> stamp
|
||||
]]
|
||||
function AdvDupe2.GenerateDupeStamp(ply)
|
||||
local stamp = {}
|
||||
stamp.name = ply:GetName()
|
||||
stamp.time = os.date("%I:%M %p")
|
||||
stamp.date = os.date("%d %B %Y")
|
||||
stamp.timezone = os.date("%z")
|
||||
hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp)
|
||||
return stamp
|
||||
end
|
||||
|
||||
function AdvDupe2.SanitizeFilename(filename)
|
||||
filename = string.gsub( filename, "[\":]", "_" )
|
||||
filename = string.gsub( filename, "%s+", " " )
|
||||
filename = string.gsub( filename, "%s*([\\/%.])%s*", "%1" )
|
||||
return filename
|
||||
end
|
||||
|
||||
local function makeInfo(tbl)
|
||||
local info = ""
|
||||
for k, v in pairs(tbl) do
|
||||
info = concat{info,k,"\1",v,"\1"}
|
||||
end
|
||||
return info.."\2"
|
||||
end
|
||||
|
||||
local AD2FF = "AD2F%s\n%s\n%s"
|
||||
|
||||
local tables, buff
|
||||
|
||||
local function noserializer() end
|
||||
|
||||
local enc = {}
|
||||
for i = 1, 255 do enc[i] = noserializer end
|
||||
|
||||
local function isArray(tbl)
|
||||
local ret = true
|
||||
local m = 0
|
||||
|
||||
for k, v in pairs(tbl) do
|
||||
m = m + 1
|
||||
if k ~= m or enc[TypeID(v)] == noserializer then
|
||||
ret = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
local function write(obj)
|
||||
enc[TypeID(obj)](obj)
|
||||
end
|
||||
|
||||
local len, tables, tablesLookup
|
||||
|
||||
enc[TYPE_TABLE] = function(obj) --table
|
||||
if not tablesLookup[obj] then
|
||||
tables = tables + 1
|
||||
tablesLookup[obj] = tables
|
||||
else
|
||||
buff:WriteByte(247)
|
||||
buff:WriteShort(tablesLookup[obj])
|
||||
return
|
||||
end
|
||||
|
||||
if isArray(obj) then
|
||||
buff:WriteByte(254)
|
||||
for i, v in pairs(obj) do
|
||||
write(v)
|
||||
end
|
||||
else
|
||||
buff:WriteByte(255)
|
||||
for k, v in pairs(obj) do
|
||||
if(enc[TypeID(k)] ~= noserializer and enc[TypeID(v)] ~= noserializer) then
|
||||
write(k)
|
||||
write(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
buff:WriteByte(246)
|
||||
end
|
||||
|
||||
enc[TYPE_BOOL] = function(obj) --boolean
|
||||
buff:WriteByte(obj and 253 or 252)
|
||||
end
|
||||
|
||||
enc[TYPE_NUMBER] = function(obj) --number
|
||||
buff:WriteByte(251)
|
||||
buff:WriteDouble(obj)
|
||||
end
|
||||
|
||||
enc[TYPE_VECTOR] = function(obj) --vector
|
||||
buff:WriteByte(250)
|
||||
buff:WriteDouble(obj.x)
|
||||
buff:WriteDouble(obj.y)
|
||||
buff:WriteDouble(obj.z)
|
||||
end
|
||||
|
||||
enc[TYPE_ANGLE] = function(obj) --angle
|
||||
buff:WriteByte(249)
|
||||
buff:WriteDouble(obj.p)
|
||||
buff:WriteDouble(obj.y)
|
||||
buff:WriteDouble(obj.r)
|
||||
end
|
||||
|
||||
enc[TYPE_STRING] = function(obj) --string
|
||||
len = #obj
|
||||
if len < 246 then
|
||||
buff:WriteByte(len)
|
||||
buff:Write(obj)
|
||||
else
|
||||
buff:WriteByte(248)
|
||||
buff:WriteULong(len)
|
||||
buff:Write(obj)
|
||||
end
|
||||
end
|
||||
|
||||
local function error_nodeserializer()
|
||||
buff:Seek(buff:Tell()-1)
|
||||
error(format("Couldn't find deserializer for type {typeid:%d}!", buff:ReadByte()))
|
||||
end
|
||||
|
||||
local reference = 0
|
||||
local read4, read5
|
||||
|
||||
do --Version 4
|
||||
local dec = {}
|
||||
for i = 1, 255 do dec[i] = error_nodeserializer end
|
||||
|
||||
local function read()
|
||||
local tt = buff:ReadByte()
|
||||
if not tt then
|
||||
error("Expected value, got EOF!")
|
||||
end
|
||||
if tt == 0 then
|
||||
return nil
|
||||
end
|
||||
return dec[tt]()
|
||||
end
|
||||
read4 = read
|
||||
|
||||
dec[255] = function() --table
|
||||
local t = {}
|
||||
local k
|
||||
reference = reference + 1
|
||||
local ref = reference
|
||||
repeat
|
||||
k = read()
|
||||
if k ~= nil then
|
||||
t[k] = read()
|
||||
end
|
||||
until (k == nil)
|
||||
tables[ref] = t
|
||||
return t
|
||||
end
|
||||
|
||||
dec[254] = function() --array
|
||||
local t = {}
|
||||
local k = 0
|
||||
local v
|
||||
reference = reference + 1
|
||||
local ref = reference
|
||||
repeat
|
||||
k = k + 1
|
||||
v = read()
|
||||
if(v ~= nil) then
|
||||
t[k] = v
|
||||
end
|
||||
|
||||
until (v == nil)
|
||||
tables[ref] = t
|
||||
return t
|
||||
end
|
||||
|
||||
dec[253] = function()
|
||||
return true
|
||||
end
|
||||
dec[252] = function()
|
||||
return false
|
||||
end
|
||||
dec[251] = function()
|
||||
return buff:ReadDouble()
|
||||
end
|
||||
dec[250] = function()
|
||||
return Vector(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble())
|
||||
end
|
||||
dec[249] = function()
|
||||
return Angle(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble())
|
||||
end
|
||||
dec[248] = function() --null-terminated string
|
||||
local start = buff:Tell()
|
||||
local slen = 0
|
||||
|
||||
while buff:ReadByte() ~= 0 do
|
||||
slen = slen + 1
|
||||
end
|
||||
|
||||
buff:Seek(start)
|
||||
|
||||
local retv = buff:Read(slen)
|
||||
if(not retv) then retv="" end
|
||||
buff:ReadByte()
|
||||
|
||||
return retv
|
||||
end
|
||||
dec[247] = function() --table reference
|
||||
reference = reference + 1
|
||||
return tables[buff:ReadShort()]
|
||||
end
|
||||
|
||||
for i = 1, 246 do dec[i] = function() return buff:Read(i) end end
|
||||
end
|
||||
|
||||
do --Version 5
|
||||
local dec = {}
|
||||
for i = 1, 255 do dec[i] = error_nodeserializer end
|
||||
|
||||
local function read()
|
||||
local tt = buff:ReadByte()
|
||||
if not tt then
|
||||
error("Expected value, got EOF!")
|
||||
end
|
||||
return dec[tt]()
|
||||
end
|
||||
read5 = read
|
||||
|
||||
dec[255] = function() --table
|
||||
local t = {}
|
||||
reference = reference + 1
|
||||
tables[reference] = t
|
||||
|
||||
for k in read do
|
||||
t[k] = read()
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
dec[254] = function() --array
|
||||
local t = {}
|
||||
reference = reference + 1
|
||||
tables[reference] = t
|
||||
|
||||
local k = 1
|
||||
for v in read do
|
||||
t[k] = v
|
||||
k = k + 1
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
dec[253] = function()
|
||||
return true
|
||||
end
|
||||
dec[252] = function()
|
||||
return false
|
||||
end
|
||||
dec[251] = function()
|
||||
return buff:ReadDouble()
|
||||
end
|
||||
dec[250] = function()
|
||||
return Vector(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble())
|
||||
end
|
||||
dec[249] = function()
|
||||
return Angle(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble())
|
||||
end
|
||||
dec[248] = function() -- Length>246 string
|
||||
local slen = buff:ReadULong()
|
||||
local retv = buff:Read(slen)
|
||||
if(not retv) then retv = "" end
|
||||
return retv
|
||||
end
|
||||
dec[247] = function() --table reference
|
||||
return tables[buff:ReadShort()]
|
||||
end
|
||||
dec[246] = function() --nil
|
||||
return
|
||||
end
|
||||
|
||||
for i = 1, 245 do dec[i] = function() return buff:Read(i) end end
|
||||
|
||||
dec[0] = function() return "" end
|
||||
end
|
||||
|
||||
local function serialize(tbl)
|
||||
tables = 0
|
||||
tablesLookup = {}
|
||||
|
||||
buff = file.Open("ad2temp.txt", "wb", "DATA")
|
||||
if not buff then error("Failed to open file data/ad2temp.txt for writing!") end
|
||||
write(tbl)
|
||||
buff:Close()
|
||||
|
||||
buff = file.Open("ad2temp.txt","rb","DATA")
|
||||
if not buff then error("Failed to open file data/ad2temp.txt for reading!") end
|
||||
local ret = buff:Read(buff:Size())
|
||||
buff:Close()
|
||||
return ret
|
||||
end
|
||||
|
||||
|
||||
local function deserialize(str, read)
|
||||
|
||||
if(str == nil) then
|
||||
error("File could not be decompressed!")
|
||||
return {}
|
||||
end
|
||||
|
||||
tables = {}
|
||||
reference = 0
|
||||
buff = file.Open("ad2temp.txt","wb","DATA")
|
||||
if not buff then error("Failed to open file data/ad2temp.txt for writing!") end
|
||||
buff:Write(str)
|
||||
buff:Flush()
|
||||
buff:Close()
|
||||
|
||||
buff = file.Open("ad2temp.txt","rb", "DATA")
|
||||
if not buff then error("Failed to open file data/ad2temp.txt for reading!") end
|
||||
local success, tbl = pcall(read)
|
||||
buff:Close()
|
||||
|
||||
if success then
|
||||
return tbl
|
||||
else
|
||||
error(tbl)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: Encode
|
||||
Desc: Generates the string for a dupe file with the given data.
|
||||
Params: <table> dupe, <table> info, <function> callback, <...> args
|
||||
Return: runs callback(<string> encoded_dupe, <...> args)
|
||||
]]
|
||||
function AdvDupe2.Encode(dupe, info, callback, ...)
|
||||
local encodedTable = compress(serialize(dupe))
|
||||
info.check = "\r\n\t\n"
|
||||
info.size = #encodedTable
|
||||
|
||||
callback(AD2FF:format(char(REVISION), makeInfo(info), encodedTable),...)
|
||||
end
|
||||
|
||||
--seperates the header and body and converts the header to a table
|
||||
local function getInfo(str)
|
||||
local last = str:find("\2")
|
||||
if not last then
|
||||
error("Attempt to read AD2 file with malformed info block!")
|
||||
end
|
||||
local info = {}
|
||||
local ss = str:sub(1, last - 1)
|
||||
for k, v in ss:gmatch("(.-)\1(.-)\1") do
|
||||
info[k] = v
|
||||
end
|
||||
|
||||
if info.check ~= "\r\n\t\n" then
|
||||
if info.check == "\10\9\10" then
|
||||
error("Detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)!")
|
||||
elseif info.check ~= nil then
|
||||
error("Detected AD2 file corrupted by newline replacements (copy/pasting the data in various editors can cause this!)")
|
||||
else
|
||||
error("Attempt to read AD2 file with malformed info block!")
|
||||
end
|
||||
end
|
||||
return info, str:sub(last+2)
|
||||
end
|
||||
|
||||
--decoders for individual versions go here
|
||||
local versions = {}
|
||||
|
||||
versions[1] = AdvDupe2.LegacyDecoders[1]
|
||||
versions[2] = AdvDupe2.LegacyDecoders[2]
|
||||
|
||||
versions[3] = function(encodedDupe)
|
||||
encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t")
|
||||
encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t")
|
||||
encodedDupe = encodedDupe:Replace("\r\n", "\n")
|
||||
encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n")
|
||||
return versions[4](encodedDupe)
|
||||
end
|
||||
|
||||
versions[4] = function(encodedDupe)
|
||||
local info, dupestring = getInfo(encodedDupe:sub(7))
|
||||
return deserialize(decompress(dupestring, AdvDupe2.MaxDupeSize), read4), info
|
||||
end
|
||||
|
||||
versions[5] = function(encodedDupe)
|
||||
local info, dupestring = getInfo(encodedDupe:sub(7))
|
||||
return deserialize(decompress(dupestring, AdvDupe2.MaxDupeSize), read5), info
|
||||
end
|
||||
|
||||
function AdvDupe2.CheckValidDupe(dupe, info)
|
||||
if not dupe.HeadEnt then return false, "Missing HeadEnt table" end
|
||||
if not dupe.Entities then return false, "Missing Entities table" end
|
||||
if not dupe.Constraints then return false, "Missing Constraints table" end
|
||||
if not dupe.HeadEnt.Z then return false, "Missing HeadEnt.Z" end
|
||||
if not dupe.HeadEnt.Pos then return false, "Missing HeadEnt.Pos" end
|
||||
if not dupe.HeadEnt.Index then return false, "Missing HeadEnt.Index" end
|
||||
if not dupe.Entities[dupe.HeadEnt.Index] then return false, "Missing HeadEnt index ["..dupe.HeadEnt.Index.."] from Entities table" end
|
||||
for key, data in pairs(dupe.Entities) do
|
||||
if not data.PhysicsObjects then return false, "Missing PhysicsObject table from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
|
||||
if not data.PhysicsObjects[0] then return false, "Missing PhysicsObject[0] table from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
|
||||
if info.ad1 then -- Advanced Duplicator 1
|
||||
if not data.PhysicsObjects[0].LocalPos then return false, "Missing PhysicsObject[0].LocalPos from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
|
||||
if not data.PhysicsObjects[0].LocalAngle then return false, "Missing PhysicsObject[0].LocalAngle from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
|
||||
else -- Advanced Duplicator 2
|
||||
if not data.PhysicsObjects[0].Pos then return false, "Missing PhysicsObject[0].Pos from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
|
||||
if not data.PhysicsObjects[0].Angle then return false, "Missing PhysicsObject[0].Angle from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
|
||||
end
|
||||
end
|
||||
return true, dupe
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: Decode
|
||||
Desc: Generates the table for a dupe from the given string. Inverse of Encode
|
||||
Params: <string> encodedDupe, <function> callback, <...> args
|
||||
Return: runs callback(<boolean> success, <table/string> tbl, <table> info)
|
||||
]]
|
||||
function AdvDupe2.Decode(encodedDupe)
|
||||
|
||||
local sig, rev = encodedDupe:match("^(....)(.)")
|
||||
|
||||
if not rev then
|
||||
return false, "Malformed dupe (wtf <5 chars long)!"
|
||||
end
|
||||
|
||||
rev = rev:byte()
|
||||
|
||||
if sig ~= "AD2F" then
|
||||
if sig == "[Inf" then --legacy support, ENGAGE (AD1 dupe detected)
|
||||
local success, tbl, info, moreinfo = pcall(AdvDupe2.LegacyDecoders[0], encodedDupe)
|
||||
|
||||
if success then
|
||||
info.ad1 = true
|
||||
info.size = #encodedDupe
|
||||
info.revision = 0
|
||||
|
||||
local index = tonumber(moreinfo.Head) or (istable(tbl.Entities) and next(tbl.Entities))
|
||||
if not index then return false, "Missing head index" end
|
||||
local pos
|
||||
if isstring(moreinfo.StartPos) then
|
||||
local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$")
|
||||
pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0)
|
||||
else
|
||||
pos = Vector()
|
||||
end
|
||||
local z
|
||||
if isstring(moreinfo.HoldPos) then
|
||||
z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1
|
||||
else
|
||||
z = 0
|
||||
end
|
||||
tbl.HeadEnt = {
|
||||
Index = index,
|
||||
Pos = pos,
|
||||
Z = z
|
||||
}
|
||||
else
|
||||
ErrorNoHalt(tbl)
|
||||
end
|
||||
|
||||
if success then
|
||||
success, tbl = AdvDupe2.CheckValidDupe(tbl, info)
|
||||
end
|
||||
|
||||
return success, tbl, info, moreinfo
|
||||
else
|
||||
return false, "Unknown duplication format!"
|
||||
end
|
||||
elseif rev > REVISION then
|
||||
return false, format("Newer codec needed. (have rev %u, need rev %u) Update Advdupe2.",REVISION,rev)
|
||||
elseif rev < 1 then
|
||||
return false, format("Attempt to use an invalid format revision (rev %d)!", rev)
|
||||
else
|
||||
local success, tbl, info = pcall(versions[rev], encodedDupe)
|
||||
|
||||
if success then
|
||||
success, tbl = AdvDupe2.CheckValidDupe(tbl, info)
|
||||
end
|
||||
if success then
|
||||
info.revision = rev
|
||||
end
|
||||
|
||||
return success, tbl, info
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
|
||||
concommand.Add("advdupe2_to_json", function(_,_,arg)
|
||||
if not arg[1] then print("Need AdvDupe2 file name argument!") return end
|
||||
local readFileName = "advdupe2/"..arg[1]
|
||||
local writeFileName = "advdupe2/"..string.StripExtension(arg[1])..".json"
|
||||
|
||||
writeFileName = AdvDupe2.SanitizeFilename(writeFileName)
|
||||
|
||||
local readFile = file.Open(readFileName, "rb", "DATA")
|
||||
if not readFile then print("File could not be read or found! ("..readFileName..")") return end
|
||||
local readData = readFile:Read(readFile:Size())
|
||||
readFile:Close()
|
||||
local ok, tbl = AdvDupe2.Decode(readData)
|
||||
local writeFile = file.Open(writeFileName, "wb", "DATA")
|
||||
if not writeFile then print("File could not be written! ("..writeFileName..")") return end
|
||||
writeFile:Write(util.TableToJSON(tbl))
|
||||
writeFile:Close()
|
||||
print("File written! ("..writeFileName..")")
|
||||
end)
|
||||
|
||||
concommand.Add("advdupe2_from_json", function(_,_,arg)
|
||||
if not arg[1] then print("Need json file name argument!") return end
|
||||
local readFileName = "advdupe2/"..arg[1]
|
||||
local writeFileName = "advdupe2/"..string.StripExtension(arg[1])..".txt"
|
||||
|
||||
local readFile = file.Open(readFileName, "rb", "DATA")
|
||||
if not readFile then print("File could not be read or found! ("..readFileName..")") return end
|
||||
local readData = readFile:Read(readFile:Size())
|
||||
readFile:Close()
|
||||
|
||||
AdvDupe2.Encode(util.JSONToTable(readData), {}, function(data)
|
||||
local writeFile = file.Open(writeFileName, "wb", "DATA")
|
||||
if not writeFile then print("File could not be written! ("..writeFileName..")") return end
|
||||
writeFile:Write(data)
|
||||
writeFile:Close()
|
||||
print("File written! ("..writeFileName..")")
|
||||
end)
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,559 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Codec Legacy Support
|
||||
|
||||
Desc: Facilitates opening of dupes from AD1 and earlier AD2 versions.
|
||||
|
||||
Author: emspike
|
||||
|
||||
Version: 2.0
|
||||
]]
|
||||
|
||||
local pairs = pairs
|
||||
local type = type
|
||||
local tonumber = tonumber
|
||||
local error = error
|
||||
local Vector = Vector
|
||||
local Angle = Angle
|
||||
local format = string.format
|
||||
local char = string.char
|
||||
local byte = string.byte
|
||||
local sub = string.sub
|
||||
local gsub = string.gsub
|
||||
local find = string.find
|
||||
local gmatch = string.gmatch
|
||||
local match = string.match
|
||||
local concat = table.concat
|
||||
|
||||
--[[
|
||||
Name: GenerateDupeStamp
|
||||
Desc: Generates an info table.
|
||||
Params: <player> ply
|
||||
Return: <table> stamp
|
||||
]]
|
||||
function AdvDupe2.GenerateDupeStamp(ply)
|
||||
local stamp = {}
|
||||
stamp.name = ply:GetName()
|
||||
stamp.time = os.date("%I:%M %p")
|
||||
stamp.date = os.date("%d %B %Y")
|
||||
stamp.timezone = os.date("%z")
|
||||
hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp)
|
||||
return stamp
|
||||
end
|
||||
|
||||
local AD2FF = "AD2F%s\n%s\n%s"
|
||||
|
||||
local decode_types_v1, decode_types_v2
|
||||
local tables = 0
|
||||
local str,pos
|
||||
local a,b,c,m,n,w,tblref
|
||||
|
||||
local function read_v2()
|
||||
local t = byte(str, pos+1)
|
||||
if t then
|
||||
local dt = decode_types_v2[t]
|
||||
if dt then
|
||||
pos = pos + 1
|
||||
return dt()
|
||||
else
|
||||
error(format("encountered invalid data type (%u)\n",t))
|
||||
end
|
||||
else
|
||||
error("expected value, got EOF\n")
|
||||
end
|
||||
end
|
||||
|
||||
decode_types_v2 = {
|
||||
[1 ] = function()
|
||||
error("expected value, got terminator\n")
|
||||
end,
|
||||
[2 ] = function() -- table
|
||||
|
||||
m = find(str, "\1", pos)
|
||||
if m then
|
||||
w = sub(str, pos+1, m-1)
|
||||
pos = m
|
||||
else
|
||||
error("expected table identifier, got EOF\n")
|
||||
end
|
||||
|
||||
local t = {}
|
||||
tables[w] = t
|
||||
|
||||
while true do
|
||||
if byte(str, pos+1) == 1 then
|
||||
pos = pos + 1
|
||||
return t
|
||||
else
|
||||
t[read_v2()] = read_v2()
|
||||
end
|
||||
end
|
||||
end,
|
||||
[3 ] = function() -- array
|
||||
|
||||
m = find(str, "\1", pos)
|
||||
if m then
|
||||
w = sub(str, pos+1, m-1)
|
||||
pos = m
|
||||
else
|
||||
error("expected table identifier, got EOF\n")
|
||||
end
|
||||
|
||||
local t, i = {}, 1
|
||||
|
||||
tables[w] = t
|
||||
|
||||
while true do
|
||||
if byte(str,pos+1) == 1 then
|
||||
pos = pos+1
|
||||
return t
|
||||
else
|
||||
t[i] = read_v2()
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end,
|
||||
[4 ] = function() -- false boolean
|
||||
return false
|
||||
end,
|
||||
[5 ] = function() -- true boolean
|
||||
return true
|
||||
end,
|
||||
[6 ] = function() -- number
|
||||
m = find(str, "\1", pos)
|
||||
if m then
|
||||
a = tonumber(sub(str, pos+1, m-1)) or 0
|
||||
pos = m
|
||||
return a
|
||||
else
|
||||
error("expected number, got EOF\n")
|
||||
end
|
||||
end,
|
||||
[7 ] = function() -- string
|
||||
m = find(str,"\1",pos)
|
||||
if m then
|
||||
w = sub(str, pos+1, m-1)
|
||||
pos = m
|
||||
return w
|
||||
else
|
||||
error("expected string, got EOF\n")
|
||||
end
|
||||
end,
|
||||
[8 ] = function() -- Vector
|
||||
m,n = find(str,".-\1.-\1.-\1", pos)
|
||||
if m then
|
||||
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
|
||||
pos = n
|
||||
return Vector(tonumber(a), tonumber(b), tonumber(c))
|
||||
else
|
||||
error("expected vector, got EOF\n")
|
||||
end
|
||||
end,
|
||||
[9 ] = function() -- Angle
|
||||
m,n = find(str, ".-\1.-\1.-\1", pos)
|
||||
if m then
|
||||
a,b,c = match(str, "^(.-)\1(.-)\1(.-)\1",pos+1)
|
||||
pos = n
|
||||
return Angle(tonumber(a), tonumber(b), tonumber(c))
|
||||
else
|
||||
error("expected angle, got EOF\n")
|
||||
end
|
||||
end,
|
||||
[10 ] = function() -- Table Reference
|
||||
m = find(str,"\1",pos)
|
||||
if m then
|
||||
w = sub(str,pos+1,m-1)
|
||||
pos = m
|
||||
else
|
||||
error("expected table identifier, got EOF\n")
|
||||
end
|
||||
tblref = tables[w]
|
||||
|
||||
if tblref then
|
||||
return tblref
|
||||
else
|
||||
error(format("table identifier %s points to nil\n", w))
|
||||
end
|
||||
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
|
||||
local function read_v1()
|
||||
local t = byte(str,pos+1)
|
||||
if t then
|
||||
local dt = decode_types_v1[t]
|
||||
if dt then
|
||||
pos = pos + 1
|
||||
return dt()
|
||||
else
|
||||
error(format("encountered invalid data type (%u)\n",t))
|
||||
end
|
||||
else
|
||||
error("expected value, got EOF\n")
|
||||
end
|
||||
end
|
||||
|
||||
decode_types_v1 = {
|
||||
[1 ] = function()
|
||||
error("expected value, got terminator\n")
|
||||
end,
|
||||
[2 ] = function() -- table
|
||||
local t = {}
|
||||
while true do
|
||||
if byte(str,pos+1) == 1 then
|
||||
pos = pos+1
|
||||
return t
|
||||
else
|
||||
t[read_v1()] = read_v1()
|
||||
end
|
||||
end
|
||||
end,
|
||||
[3 ] = function() -- array
|
||||
local t, i = {}, 1
|
||||
while true do
|
||||
if byte(str,pos+1) == 1 then
|
||||
pos = pos+1
|
||||
return t
|
||||
else
|
||||
t[i] = read_v1()
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end,
|
||||
[4 ] = function() -- false boolean
|
||||
return false
|
||||
end,
|
||||
[5 ] = function() -- true boolean
|
||||
return true
|
||||
end,
|
||||
[6 ] = function() -- number
|
||||
m = find(str,"\1",pos)
|
||||
if m then
|
||||
a = tonumber(sub(str,pos+1,m-1)) or 0
|
||||
pos = m
|
||||
return a
|
||||
else
|
||||
error("expected number, got EOF\n")
|
||||
end
|
||||
end,
|
||||
[7 ] = function() -- string
|
||||
m = find(str,"\1",pos)
|
||||
if m then
|
||||
w = sub(str,pos+1,m-1)
|
||||
pos = m
|
||||
return w
|
||||
else
|
||||
error("expected string, got EOF\n")
|
||||
end
|
||||
end,
|
||||
[8 ] = function() -- Vector
|
||||
m,n = find(str,".-\1.-\1.-\1",pos)
|
||||
if m then
|
||||
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
|
||||
pos = n
|
||||
return Vector(tonumber(a), tonumber(b), tonumber(c))
|
||||
else
|
||||
error("expected vector, got EOF\n")
|
||||
end
|
||||
end,
|
||||
[9 ] = function() -- Angle
|
||||
m,n = find(str,".-\1.-\1.-\1",pos)
|
||||
if m then
|
||||
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
|
||||
pos = n
|
||||
return Angle(tonumber(a), tonumber(b), tonumber(c))
|
||||
else
|
||||
error("expected angle, got EOF\n")
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local function deserialize_v1(data)
|
||||
str = data
|
||||
pos = 0
|
||||
tables = {}
|
||||
return read_v1()
|
||||
end
|
||||
|
||||
local function deserialize_v2(data)
|
||||
str = data
|
||||
pos = 0
|
||||
tables = {}
|
||||
return read_v2()
|
||||
end
|
||||
|
||||
local function lzwDecode(encoded)
|
||||
local dictionary_length = 256
|
||||
local dictionary = {}
|
||||
for i = 0, 255 do
|
||||
dictionary[i] = char(i)
|
||||
end
|
||||
|
||||
local pos = 2
|
||||
local decompressed = {}
|
||||
local decompressed_length = 1
|
||||
|
||||
local index = byte(encoded)
|
||||
local word = dictionary[index]
|
||||
|
||||
decompressed[decompressed_length] = dictionary[index]
|
||||
|
||||
local entry
|
||||
local encoded_length = #encoded
|
||||
local firstbyte --of an index
|
||||
while pos <= encoded_length do
|
||||
firstbyte = byte(encoded,pos)
|
||||
if firstbyte > 252 then --now we know it's a length indicator for a multibyte index
|
||||
index = 0
|
||||
firstbyte = 256 - firstbyte
|
||||
|
||||
--[[if pos+firstbyte > encoded_length then --will test for performance impact
|
||||
error("expected index got EOF")
|
||||
end]]
|
||||
|
||||
for i = pos+firstbyte, pos+1, -1 do
|
||||
index = bit.bor(bit.lshift(index, 8), byte(encoded,i))
|
||||
end
|
||||
pos = pos + firstbyte + 1
|
||||
else
|
||||
index = firstbyte
|
||||
pos = pos + 1
|
||||
end
|
||||
entry = dictionary[index] or (word..sub(word,1,1))
|
||||
decompressed_length = decompressed_length + 1
|
||||
decompressed[decompressed_length] = entry
|
||||
dictionary[dictionary_length] = word..sub(entry,1,1)
|
||||
dictionary_length = dictionary_length + 1
|
||||
word = entry
|
||||
end
|
||||
return concat(decompressed)
|
||||
end
|
||||
|
||||
--http://en.wikipedia.org/wiki/Huffman_coding#Decompression
|
||||
|
||||
local invcodes = {[2]={[0]="\254"},[5]={[22]="\1",[11]="\2"},[6]={[13]="\7",[35]="\6",[37]="\5",[58]="\3",[31]="\8",[9]="\13",[51]="\9",[55]="\10",[57]="\4",[59]="\15"},[7]={[1]="\14",[15]="\16",[87]="\31",[89]="\30",[62]="\26",[17]="\27",[97]="\19",[19]="\43",[10]="\12",[39]="\33",[41]="\24",[82]="\40",[3]="\32",[46]="\41",[47]="\38",[94]="\25",[65]="\23",[50]="\39",[26]="\11",[7]="\28",[33]="\18",[61]="\17",[25]="\42"},[8]={[111]="\101",[162]="\29",[2]="\34",[133]="\21",[142]="\36",[5]="\20",[21]="\37",[170]="\44",[130]="\22",[66]="\35"},[9]={[241]="\121",[361]="\104",[365]="\184",[125]="\227",[373]="\198",[253]="\117",[381]="\57",[270]="\49",[413]="\80",[290]="\47",[294]="\115",[38]="\112",[429]="\74",[433]="\0",[437]="\48",[158]="\183",[453]="\107",[166]="\111",[469]="\182",[477]="\241",[45]="\86",[489]="\69",[366]="\100",[497]="\61",[509]="\76",[49]="\53",[390]="\78",[279]="\196",[283]="\70",[414]="\98",[53]="\55",[422]="\109",[233]="\79",[349]="\89",[369]="\52",[14]="\105",[238]="\56",[319]="\162",[323]="\83",[327]="\63",[458]="\65",[335]="\231",[339]="\225",[337]="\114",[347]="\193",[493]="\139",[23]="\209",[359]="\250",[490]="\68",[42]="\54",[63]="\91",[286]="\97",[254]="\50",[510]="\108",[109]="\73",[67]="\103",[255]="\122",[69]="\170",[70]="\110",[407]="\176",[411]="\119",[110]="\120",[83]="\146",[149]="\163",[151]="\224",[85]="\51",[155]="\177",[79]="\251",[27]="\118",[447]="\159",[451]="\228",[455]="\175",[383]="\174",[463]="\243",[467]="\157",[173]="\210",[475]="\167",[177]="\84",[90]="\45",[487]="\206",[93]="\226",[495]="\245",[207]="\64",[127]="\147",[191]="\155",[511]="\153",[195]="\208",[197]="\85",[199]="\178",[181]="\82",[102]="\116",[103]="\71",[285]="\144",[105]="\102",[211]="\199",[213]="\123",[301]="\66",[305]="\46",[219]="\137",[81]="\67",[91]="\88",[157]="\130",[325]="\95",[29]="\58",[231]="\201",[117]="\99",[341]="\222",[237]="\77",[239]="\211",[71]="\223"},[10]={[710]="\149",[245]="\60",[742]="\172",[774]="\81",[134]="\151",[917]="\145",[274]="\216",[405]="\242",[146]="\194",[838]="\246",[298]="\248",[870]="\189",[1013]="\150",[894]="\190",[326]="\244",[330]="\166",[334]="\217",[465]="\179",[346]="\59",[354]="\180",[966]="\212",[974]="\143",[370]="\148",[998]="\154",[625]="\138",[382]="\161",[194]="\141",[198]="\126",[402]="\96",[206]="\185",[586]="\129",[721]="\187",[610]="\135",[618]="\181",[626]="\72",[226]="\62",[454]="\127",[658]="\113",[462]="\164",[234]="\214",[474]="\140",[242]="\106",[714]="\188",[730]="\87",[498]="\237",[746]="\125",[754]="\229",[786]="\128",[202]="\93",[18]="\255",[810]="\173",[846]="\131",[74]="\192",[842]="\142",[977]="\252",[858]="\235",[78]="\134",[874]="\234",[882]="\90",[646]="\92",[1006]="\160",[126]="\165",[914]="\221",[718]="\94",[738]="\238",[638]="\197",[482]="\230",[34]="\220",[962]="\133",[6]="\213",[706]="\219",[986]="\171",[994]="\233",[866]="\200",[1010]="\247",[98]="\169",[518]="\236",[494]="\207",[230]="\205",[542]="\191",[501]="\202",[530]="\203",[450]="\204",[209]="\158",[106]="\186",[590]="\136",[218]="\232",[733]="\124",[309]="\168",[221]="\152",[757]="\240",[113]="\215",[114]="\156",[362]="\239",[486]="\132",[358]="\249",[262]="\75",[30]="\218",[821]="\195",[546]="\253"}}
|
||||
|
||||
local function huffmanDecode(encoded)
|
||||
|
||||
local h1,h2,h3 = byte(encoded, 1, 3)
|
||||
|
||||
if (not h3) or (#encoded < 4) then
|
||||
error("invalid input")
|
||||
end
|
||||
|
||||
local original_length = bit.bor(bit.lshift(h3,16), bit.lshift(h2,8), h1)
|
||||
local encoded_length = #encoded+1
|
||||
local decoded = {}
|
||||
local decoded_length = 0
|
||||
local buffer = 0
|
||||
local buffer_length = 0
|
||||
local code
|
||||
local code_len = 2
|
||||
local temp
|
||||
local pos = 4
|
||||
|
||||
while decoded_length < original_length do
|
||||
if code_len <= buffer_length then
|
||||
temp = invcodes[code_len]
|
||||
code = bit.band(buffer, bit.lshift(1, code_len)-1)
|
||||
if temp and temp[code] then --most of the time temp is nil
|
||||
decoded_length = decoded_length + 1
|
||||
decoded[decoded_length] = temp[code]
|
||||
buffer = bit.rshift(buffer, code_len)
|
||||
buffer_length = buffer_length - code_len
|
||||
code_len = 2
|
||||
else
|
||||
code_len = code_len + 1
|
||||
if code_len > 10 then
|
||||
error("malformed code")
|
||||
end
|
||||
end
|
||||
else
|
||||
buffer = bit.bor(buffer, bit.lshift(byte(encoded, pos), buffer_length))
|
||||
buffer_length = buffer_length + 8
|
||||
pos = pos + 1
|
||||
if pos > encoded_length then
|
||||
error("malformed code")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return concat(decoded)
|
||||
end
|
||||
|
||||
local function invEscapeSub(str)
|
||||
local escseq,body = match(str,"^(.-)\n(.-)$")
|
||||
|
||||
if not escseq then error("invalid input") end
|
||||
|
||||
return gsub(body,escseq,"\26")
|
||||
end
|
||||
|
||||
local dictionary
|
||||
local subtables
|
||||
|
||||
local function deserializeChunk(chunk)
|
||||
|
||||
local ctype,val = byte(chunk),sub(chunk,3)
|
||||
|
||||
if ctype == 89 then return dictionary[ val ]
|
||||
elseif ctype == 86 then
|
||||
local a,b,c = match(val,"^(.-),(.-),(.+)$")
|
||||
return Vector( tonumber(a), tonumber(b), tonumber(c) )
|
||||
elseif ctype == 65 then
|
||||
local a,b,c = match(val,"^(.-),(.-),(.+)$")
|
||||
return Angle( tonumber(a), tonumber(b), tonumber(c) )
|
||||
elseif ctype == 84 then
|
||||
local t = {}
|
||||
local tv = subtables[val]
|
||||
if not tv then
|
||||
tv = {}
|
||||
subtables[ val ] = tv
|
||||
end
|
||||
tv[#tv+1] = t
|
||||
return t
|
||||
elseif ctype == 78 then return tonumber(val)
|
||||
elseif ctype == 83 then return gsub(sub(val,2,-2),"»",";")
|
||||
elseif ctype == 66 then return val == "t"
|
||||
elseif ctype == 80 then return 1
|
||||
end
|
||||
|
||||
error(format("AD1 deserialization failed: invalid chunk (%u:%s)",ctype,val))
|
||||
|
||||
end
|
||||
|
||||
local function deserializeAD1(dupestring)
|
||||
|
||||
dupestring = dupestring:Replace("\r\n", "\n")
|
||||
local header, extraHeader, dupeBlock, dictBlock = dupestring:match("%[Info%]\n(.+)\n%[More Information%]\n(.+)\n%[Save%]\n(.+)\n%[Dict%]\n(.+)")
|
||||
|
||||
if not header then
|
||||
error("unknown duplication format")
|
||||
end
|
||||
|
||||
local info = {}
|
||||
for k,v in header:gmatch("([^\n:]+):([^\n]+)") do
|
||||
info[k] = v
|
||||
end
|
||||
|
||||
local moreinfo = {}
|
||||
for k,v in extraHeader:gmatch("([^\n:]+):([^\n]+)") do
|
||||
moreinfo[k] = v
|
||||
end
|
||||
|
||||
dictionary = {}
|
||||
for k,v in dictBlock:gmatch("(.-):\"(.-)\"\n") do
|
||||
dictionary[k] = v
|
||||
end
|
||||
|
||||
local dupe = {}
|
||||
for key,block in dupeBlock:gmatch("([^\n:]+):([^\n]+)") do
|
||||
|
||||
local tables = {}
|
||||
subtables = {}
|
||||
local head
|
||||
|
||||
for id,chunk in block:gmatch('(%w+){(.-)}') do
|
||||
|
||||
--check if this table is the trunk
|
||||
if byte(id) == 72 then
|
||||
id = sub(id,2)
|
||||
head = id
|
||||
end
|
||||
|
||||
tables[id] = {}
|
||||
|
||||
for kv in gmatch(chunk,'[^;]+') do
|
||||
|
||||
local k,v = match(kv,'(.-)=(.+)')
|
||||
|
||||
if k then
|
||||
k = deserializeChunk( k )
|
||||
v = deserializeChunk( v )
|
||||
|
||||
tables[id][k] = v
|
||||
else
|
||||
v = deserializeChunk( kv )
|
||||
local tid = tables[id]
|
||||
tid[#tid+1]=v
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--Restore table references
|
||||
for id,tbls in pairs( subtables ) do
|
||||
for _,tbl in pairs( tbls ) do
|
||||
if not tables[id] then error("attempt to reference a nonexistent table") end
|
||||
for k,v in pairs(tables[id]) do
|
||||
tbl[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dupe[key] = tables[ head ]
|
||||
|
||||
end
|
||||
|
||||
return dupe, info, moreinfo
|
||||
|
||||
end
|
||||
|
||||
--seperates the header and body and converts the header to a table
|
||||
local function getInfo(str)
|
||||
local last = str:find("\2")
|
||||
if not last then
|
||||
error("attempt to read AD2 file with malformed info block error 1")
|
||||
end
|
||||
local info = {}
|
||||
local ss = str:sub(1,last-1)
|
||||
for k,v in ss:gmatch("(.-)\1(.-)\1") do
|
||||
info[k] = v
|
||||
end
|
||||
if info.check ~= "\r\n\t\n" then
|
||||
if info.check == "\10\9\10" then
|
||||
error("detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)")
|
||||
else
|
||||
error("attempt to read AD2 file with malformed info block error 2")
|
||||
end
|
||||
end
|
||||
return info, str:sub(last+2)
|
||||
end
|
||||
|
||||
--decoders for individual versions go here
|
||||
local versions = {}
|
||||
|
||||
versions[2] = function(encodedDupe)
|
||||
encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t")
|
||||
encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t")
|
||||
encodedDupe = encodedDupe:Replace("\r\n", "\n")
|
||||
encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n")
|
||||
local info, dupestring = getInfo(encodedDupe:sub(7))
|
||||
return deserialize_v2(
|
||||
lzwDecode(
|
||||
huffmanDecode(
|
||||
invEscapeSub(dupestring)
|
||||
)
|
||||
)
|
||||
), info
|
||||
end
|
||||
|
||||
versions[1] = function(encodedDupe)
|
||||
encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t")
|
||||
encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t")
|
||||
encodedDupe = encodedDupe:Replace("\r\n", "\n")
|
||||
encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n")
|
||||
local info, dupestring = getInfo(encodedDupe:sub(7))
|
||||
return deserialize_v1(
|
||||
lzwDecode(
|
||||
huffmanDecode(
|
||||
invEscapeSub(dupestring)
|
||||
)
|
||||
)
|
||||
), info
|
||||
end
|
||||
|
||||
versions[0] = deserializeAD1
|
||||
|
||||
AdvDupe2.LegacyDecoders = versions
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,143 @@
|
||||
--Save a file to the client
|
||||
local function SaveFile(ply, cmd, args)
|
||||
if(not ply.AdvDupe2 or not ply.AdvDupe2.Entities or next(ply.AdvDupe2.Entities)==nil)then AdvDupe2.Notify(ply,"Duplicator is empty, nothing to save.", NOTIFY_ERROR) return end
|
||||
if(not game.SinglePlayer() and CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then
|
||||
AdvDupe2.Notify(ply,"Cannot save at the moment. Please Wait...", NOTIFY_ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
if(ply.AdvDupe2.Pasting || ply.AdvDupe2.Downloading)then
|
||||
AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.",NOTIFY_ERROR)
|
||||
return false
|
||||
end
|
||||
|
||||
ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")+2)
|
||||
|
||||
local name = string.Explode("/", args[1])
|
||||
ply.AdvDupe2.Name = name[#name]
|
||||
|
||||
net.Start("AdvDupe2_SetDupeInfo")
|
||||
net.WriteString(ply.AdvDupe2.Name)
|
||||
net.WriteString(ply:Nick())
|
||||
net.WriteString(os.date("%d %B %Y"))
|
||||
net.WriteString(os.date("%I:%M %p"))
|
||||
net.WriteString("")
|
||||
net.WriteString(args[2] or "")
|
||||
net.WriteString(table.Count(ply.AdvDupe2.Entities))
|
||||
net.WriteString(#ply.AdvDupe2.Constraints)
|
||||
net.Send(ply)
|
||||
|
||||
local Tab = {Entities = ply.AdvDupe2.Entities, Constraints = ply.AdvDupe2.Constraints, HeadEnt = ply.AdvDupe2.HeadEnt, Description=args[2]}
|
||||
|
||||
AdvDupe2.Encode( Tab, AdvDupe2.GenerateDupeStamp(ply), function(data)
|
||||
AdvDupe2.SendToClient(ply, data, false)
|
||||
end)
|
||||
end
|
||||
concommand.Add("AdvDupe2_SaveFile", SaveFile)
|
||||
|
||||
function AdvDupe2.SendToClient(ply, data, autosave)
|
||||
if(not IsValid(ply))then return end
|
||||
if #data > AdvDupe2.MaxDupeSize then
|
||||
AdvDupe2.Notify(ply,"Copied duplicator filesize is too big!",NOTIFY_ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
ply.AdvDupe2.Downloading = true
|
||||
AdvDupe2.InitProgressBar(ply,"Saving:")
|
||||
|
||||
net.Start("AdvDupe2_ReceiveFile")
|
||||
net.WriteBool(autosave)
|
||||
net.WriteStream(data, function()
|
||||
ply.AdvDupe2.Downloading = false
|
||||
end)
|
||||
net.Send(ply)
|
||||
end
|
||||
|
||||
function AdvDupe2.LoadDupe(ply,success,dupe,info,moreinfo)
|
||||
if(not IsValid(ply))then return end
|
||||
|
||||
if not success then
|
||||
AdvDupe2.Notify(ply,"Could not open "..dupe,NOTIFY_ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
if(not game.SinglePlayer())then
|
||||
if(tonumber(GetConVarString("AdvDupe2_MaxConstraints"))~=0 and #dupe["Constraints"]>tonumber(GetConVarString("AdvDupe2_MaxConstraints")))then
|
||||
AdvDupe2.Notify(ply,"Amount of constraints is greater than "..GetConVarString("AdvDupe2_MaxConstraints"),NOTIFY_ERROR)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
ply.AdvDupe2.Entities = {}
|
||||
ply.AdvDupe2.Constraints = {}
|
||||
ply.AdvDupe2.HeadEnt={}
|
||||
ply.AdvDupe2.Revision = info.revision
|
||||
|
||||
if(info.ad1)then
|
||||
|
||||
ply.AdvDupe2.HeadEnt.Index = tonumber(moreinfo.Head)
|
||||
local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$")
|
||||
ply.AdvDupe2.HeadEnt.Pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0)
|
||||
local z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1
|
||||
ply.AdvDupe2.HeadEnt.Z = z
|
||||
ply.AdvDupe2.HeadEnt.Pos.Z = ply.AdvDupe2.HeadEnt.Pos.Z + z
|
||||
local Pos
|
||||
local Ang
|
||||
for k,v in pairs(dupe["Entities"])do
|
||||
Pos = nil
|
||||
Ang = nil
|
||||
if(v.SavedParentIdx)then
|
||||
if(not v.BuildDupeInfo)then v.BuildDupeInfo = {} end
|
||||
v.BuildDupeInfo.DupeParentID = v.SavedParentIdx
|
||||
Pos = v.LocalPos*1
|
||||
Ang = v.LocalAngle*1
|
||||
end
|
||||
for i,p in pairs(v.PhysicsObjects)do
|
||||
p.Pos = Pos or (p.LocalPos*1)
|
||||
p.Pos.Z = p.Pos.Z - z
|
||||
p.Angle = Ang or (p.LocalAngle*1)
|
||||
p.LocalPos = nil
|
||||
p.LocalAngle = nil
|
||||
p.Frozen = not p.Frozen -- adv dupe 2 does this wrong way
|
||||
end
|
||||
v.LocalPos = nil
|
||||
v.LocalAngle = nil
|
||||
end
|
||||
|
||||
ply.AdvDupe2.Entities = dupe["Entities"]
|
||||
ply.AdvDupe2.Constraints = dupe["Constraints"]
|
||||
|
||||
else
|
||||
ply.AdvDupe2.Entities = dupe["Entities"]
|
||||
ply.AdvDupe2.Constraints = dupe["Constraints"]
|
||||
ply.AdvDupe2.HeadEnt = dupe["HeadEnt"]
|
||||
end
|
||||
AdvDupe2.ResetOffsets(ply, true)
|
||||
end
|
||||
|
||||
local function AdvDupe2_ReceiveFile(len, ply)
|
||||
if not IsValid(ply) then return end
|
||||
if not ply.AdvDupe2 then ply.AdvDupe2 = {} end
|
||||
|
||||
ply.AdvDupe2.Name = string.match(net.ReadString(), "([%w_ ]+)") or "Advanced Duplication"
|
||||
|
||||
local stream = net.ReadStream(ply, function(data)
|
||||
if data then
|
||||
AdvDupe2.LoadDupe(ply, AdvDupe2.Decode(data))
|
||||
else
|
||||
AdvDupe2.Notify(ply, "Duplicator Upload Failed!", NOTIFY_ERROR, 5)
|
||||
end
|
||||
ply.AdvDupe2.Uploading = false
|
||||
end)
|
||||
|
||||
if ply.AdvDupe2.Uploading then
|
||||
if stream then
|
||||
stream:Remove()
|
||||
end
|
||||
AdvDupe2.Notify(ply, "Duplicator is Busy!", NOTIFY_ERROR, 5)
|
||||
elseif stream then
|
||||
ply.AdvDupe2.Uploading = true
|
||||
AdvDupe2.InitProgressBar(ply, "Uploading: ")
|
||||
end
|
||||
end
|
||||
net.Receive("AdvDupe2_ReceiveFile", AdvDupe2_ReceiveFile)
|
||||
@@ -0,0 +1,70 @@
|
||||
|
||||
util.AddNetworkString("AdvDupe2_SendGhosts")
|
||||
util.AddNetworkString("AdvDupe2_AddGhost")
|
||||
|
||||
function AdvDupe2.SendGhost(ply, AddOne)
|
||||
net.Start("AdvDupe2_AddGhost")
|
||||
net.WriteString(AddOne.Model)
|
||||
net.WriteInt(#AddOne.PhysicsObjects, 8)
|
||||
for i=0, #AddOne.PhysicsObjects do
|
||||
net.WriteAngle(AddOne.PhysicsObjects[i].Angle)
|
||||
net.WriteVector(AddOne.PhysicsObjects[i].Pos)
|
||||
end
|
||||
net.Send(ply)
|
||||
end
|
||||
|
||||
function AdvDupe2.SendGhosts(ply)
|
||||
if(not ply.AdvDupe2.Entities)then return end
|
||||
|
||||
local cache = {}
|
||||
local temp = {}
|
||||
local mdls = {}
|
||||
local cnt = 1
|
||||
local add = true
|
||||
local head
|
||||
|
||||
for k,v in pairs(ply.AdvDupe2.Entities)do
|
||||
temp[cnt] = v
|
||||
for i=1,#cache do
|
||||
if(cache[i]==v.Model)then
|
||||
mdls[cnt] = i
|
||||
add=false
|
||||
break
|
||||
end
|
||||
end
|
||||
if(add)then
|
||||
mdls[cnt] = table.insert(cache, v.Model)
|
||||
else
|
||||
add = true
|
||||
end
|
||||
if(k==ply.AdvDupe2.HeadEnt.Index)then
|
||||
head = cnt
|
||||
end
|
||||
cnt = cnt+1
|
||||
end
|
||||
|
||||
if(!head)then
|
||||
AdvDupe2.Notify(ply, "Invalid head entity for ghosts.", NOTIFY_ERROR);
|
||||
return
|
||||
end
|
||||
|
||||
net.Start("AdvDupe2_SendGhosts")
|
||||
net.WriteInt(head, 16)
|
||||
net.WriteFloat(ply.AdvDupe2.HeadEnt.Z)
|
||||
net.WriteVector(ply.AdvDupe2.HeadEnt.Pos)
|
||||
net.WriteInt(#cache, 16)
|
||||
for i=1,#cache do
|
||||
net.WriteString(cache[i])
|
||||
end
|
||||
net.WriteInt(cnt-1, 16)
|
||||
for i=1, #temp do
|
||||
net.WriteInt(mdls[i], 16)
|
||||
net.WriteInt(#temp[i].PhysicsObjects, 8)
|
||||
for k=0, #temp[i].PhysicsObjects do
|
||||
net.WriteAngle(temp[i].PhysicsObjects[k].Angle)
|
||||
net.WriteVector(temp[i].PhysicsObjects[k].Pos)
|
||||
end
|
||||
end
|
||||
net.Send(ply)
|
||||
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
--[[
|
||||
Title: Miscellaneous
|
||||
|
||||
Desc: Contains miscellaneous (serverside) things AD2 needs to function that don't fit anywhere else.
|
||||
|
||||
Author: TB
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
--[[
|
||||
Name: SavePositions
|
||||
Desc: Save the position of the entities to prevent sagging on dupe.
|
||||
Params: <entity> Constraint
|
||||
Returns: nil
|
||||
]]
|
||||
|
||||
local function SavePositions( Constraint )
|
||||
|
||||
if IsValid(Constraint) then
|
||||
|
||||
if Constraint.BuildDupeInfo then return end
|
||||
local BuildDupeInfo = {}
|
||||
Constraint.BuildDupeInfo = BuildDupeInfo
|
||||
|
||||
local Ent1, Ent2
|
||||
if IsValid(Constraint.Ent) then
|
||||
if Constraint.Ent:GetPhysicsObjectCount()>1 then
|
||||
BuildDupeInfo.Ent1Ang = Constraint.Ent:GetAngles()
|
||||
else
|
||||
BuildDupeInfo.Ent1Ang = Constraint.Ent:GetPhysicsObject():GetAngles()
|
||||
end
|
||||
end
|
||||
|
||||
if IsValid(Constraint.Ent1) then
|
||||
if Constraint.Ent1:GetPhysicsObjectCount()>1 then
|
||||
local Bone = Constraint.Ent1:GetPhysicsObjectNum(Constraint.Bone1)
|
||||
BuildDupeInfo.Ent1Ang = Constraint.Ent1:GetAngles()
|
||||
BuildDupeInfo.Ent1Pos = Constraint.Ent1:GetPos()
|
||||
BuildDupeInfo.Bone1 = Constraint.Bone1
|
||||
BuildDupeInfo.Bone1Pos = Bone:GetPos() - Constraint.Ent1:GetPos()
|
||||
BuildDupeInfo.Bone1Angle = Bone:GetAngles()
|
||||
else
|
||||
local Bone = Constraint.Ent1:GetPhysicsObject()
|
||||
BuildDupeInfo.Ent1Ang = Bone:GetAngles()
|
||||
BuildDupeInfo.Ent1Pos = Bone:GetPos()
|
||||
end
|
||||
|
||||
if IsValid(Constraint.Ent2) then
|
||||
if Constraint.Ent2:GetPhysicsObjectCount()>1 then
|
||||
local Bone = Constraint.Ent2:GetPhysicsObjectNum(Constraint.Bone2)
|
||||
BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Constraint.Ent2:GetPos()
|
||||
BuildDupeInfo.Ent2Ang = Constraint.Ent2:GetAngles()
|
||||
BuildDupeInfo.Bone2 = Constraint.Bone2
|
||||
BuildDupeInfo.Bone2Pos = Bone:GetPos() - Constraint.Ent2:GetPos()
|
||||
BuildDupeInfo.Bone2Angle = Bone:GetAngles()
|
||||
else
|
||||
local Bone = Constraint.Ent2:GetPhysicsObject()
|
||||
BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Bone:GetPos()
|
||||
BuildDupeInfo.Ent2Ang = Bone:GetAngles()
|
||||
end
|
||||
elseif IsValid(Constraint.Ent4) then
|
||||
if Constraint.Ent4:GetPhysicsObjectCount()>1 then
|
||||
local Bone = Constraint.Ent4:GetPhysicsObjectNum(Constraint.Bone4)
|
||||
BuildDupeInfo.Bone2 = Constraint.Bone4
|
||||
BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Constraint.Ent4:GetPos()
|
||||
BuildDupeInfo.Ent2Ang = Constraint.Ent4:GetAngles()
|
||||
BuildDupeInfo.Bone2Pos = Bone:GetPos() - Constraint.Ent4:GetPos()
|
||||
BuildDupeInfo.Bone2Angle = Bone:GetAngles()
|
||||
else
|
||||
local Bone = Constraint.Ent4:GetPhysicsObject()
|
||||
BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Bone:GetPos()
|
||||
BuildDupeInfo.Ent2Ang = Bone:GetAngles()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
local function monitorConstraint(name)
|
||||
local oldFunc = constraint[name]
|
||||
constraint[name] = function(...)
|
||||
local Constraint, b, c = oldFunc(...)
|
||||
if Constraint and Constraint:IsValid() then
|
||||
SavePositions(Constraint)
|
||||
end
|
||||
return Constraint, b, c
|
||||
end
|
||||
end
|
||||
monitorConstraint("AdvBallsocket")
|
||||
monitorConstraint("Axis")
|
||||
monitorConstraint("Ballsocket")
|
||||
monitorConstraint("Elastic")
|
||||
monitorConstraint("Hydraulic")
|
||||
monitorConstraint("Keepupright")
|
||||
monitorConstraint("Motor")
|
||||
monitorConstraint("Muscle")
|
||||
monitorConstraint("Pulley")
|
||||
monitorConstraint("Rope")
|
||||
monitorConstraint("Slider")
|
||||
monitorConstraint("Weld")
|
||||
monitorConstraint("Winch")
|
||||
@@ -0,0 +1,145 @@
|
||||
include("shared.lua")
|
||||
|
||||
surface.CreateFont( "InfoRUS2", { font = "Enhanced Dot Digital-7", extended = true, size = 90, weight = 800, antialias = true })
|
||||
surface.CreateFont( "InfoRUS3", { font = "Enhanced Dot Digital-7", extended = true, size = 50, weight = 800, antialias = true })
|
||||
|
||||
local font = "InfoRUS2"
|
||||
|
||||
local sizetable = {
|
||||
[3] = {350, 0.5},
|
||||
[4] = {470, -11.5},
|
||||
[5] = {590, -11.5},
|
||||
[6] = {710, 0.5},
|
||||
[7] = {830, 0.5},
|
||||
[8] = {950, 0.5},
|
||||
}
|
||||
|
||||
function ENT:Initialize()
|
||||
|
||||
self.OldWide = self:GetWide()
|
||||
|
||||
self.frame = vgui.Create( "DPanel" )
|
||||
self.frame:SetSize( sizetable[self:GetWide()][1], 120 )
|
||||
self.frame.Text = self:GetText()
|
||||
self.frame.Type = self:GetType()
|
||||
self.frame.col = self:GetTColor()
|
||||
self.frame.damage = 0
|
||||
self.frame.appr = nil
|
||||
self.frame.FX = self:GetFX()
|
||||
self.frame.On = self:GetOn()
|
||||
self.frame.alfa = 0
|
||||
self.frame.speed = self:GetSpeed()
|
||||
self.frame:SetPaintedManually( true )
|
||||
self.frame.Paint = function(self,w,h)
|
||||
|
||||
if self.On <= 0 then
|
||||
if self.alfa < 1 then return end
|
||||
self.alfa = Lerp(FrameTime() * 5,self.alfa,0)
|
||||
else
|
||||
if self.FX > 0 then
|
||||
self.alfa = math.random(100,220)
|
||||
else
|
||||
self.alfa = 255
|
||||
end
|
||||
end
|
||||
|
||||
surface.DisableClipping( false )
|
||||
surface.SetFont(font)
|
||||
local ww,hh = surface.GetTextSize(self.Text)
|
||||
local multiplier = self.speed * 100
|
||||
|
||||
self.static = false
|
||||
|
||||
if self.damage < CurTime() and self.On then
|
||||
if self.Type == 1 then
|
||||
|
||||
local xs = (math.fmod(SysTime() * multiplier,w+ww)) - ww
|
||||
|
||||
draw.DrawText(self.Text,font,xs,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
|
||||
elseif self.Type == 2 then
|
||||
|
||||
if !self.appr or self.appr > ww then
|
||||
self.appr = -w
|
||||
else
|
||||
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
|
||||
end
|
||||
|
||||
draw.DrawText(self.Text,font,self.appr * -1,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
|
||||
else
|
||||
if !self.appr then
|
||||
self.appr = 0
|
||||
end
|
||||
|
||||
if w > ww then
|
||||
if self.Type == 3 then
|
||||
if self.appr < w-ww and !self.refl then
|
||||
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
|
||||
else
|
||||
if self.appr <= 0 then
|
||||
self.refl = nil
|
||||
else
|
||||
self.refl = true
|
||||
self.appr = math.Approach(self.appr, 0, FrameTime() * multiplier)
|
||||
end
|
||||
end
|
||||
else
|
||||
self.static = true
|
||||
end
|
||||
else
|
||||
if self.appr > w-ww-50 and !self.refl then
|
||||
self.appr = math.Approach(self.appr, w-ww-50, FrameTime() * multiplier)
|
||||
else
|
||||
if self.appr >= 50 then
|
||||
self.refl = nil
|
||||
else
|
||||
self.refl = true
|
||||
self.appr = math.Approach(self.appr, 50, FrameTime() * multiplier)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.static then
|
||||
draw.DrawText(self.Text,font,w/2,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),1)
|
||||
else
|
||||
draw.DrawText(self.Text,font,self.appr,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
|
||||
end
|
||||
end
|
||||
else
|
||||
draw.DrawText(self.Text,font,math.random(0,w-ww),10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, math.random(0,255)),0)
|
||||
end
|
||||
surface.DisableClipping( true )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:Draw()
|
||||
|
||||
self:DrawModel()
|
||||
|
||||
if self.frame then
|
||||
self.frame.Text = self:GetText()
|
||||
self.frame.Type = self:GetType()
|
||||
self.frame.col = self:GetTColor()
|
||||
self.frame.FX = self:GetFX()
|
||||
self.frame.On = self:GetOn()
|
||||
self.frame.damage = self:GetNWInt("LastDamaged")
|
||||
self.frame.speed = self:GetSpeed()
|
||||
end
|
||||
|
||||
local Pos = self:GetPos()
|
||||
local Ang = self:GetAngles()
|
||||
local hight = 12
|
||||
|
||||
if self.OldWide != self:GetWide() then
|
||||
self.frame:SetSize( sizetable[self:GetWide()][1], 120 )
|
||||
self.OldWide = self:GetWide()
|
||||
end
|
||||
|
||||
if self:GetWide() == 3 then
|
||||
hight = 6
|
||||
end
|
||||
|
||||
cam.Start3D2D(Pos + Ang:Up() * 1.1 - Ang:Right() * hight + Ang:Forward() * sizetable[self:GetWide()][2], Ang, 0.1)
|
||||
self.frame:PaintManual()
|
||||
cam.End3D2D()
|
||||
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
AddCSLuaFile("cl_init.lua")
|
||||
AddCSLuaFile("shared.lua")
|
||||
include("shared.lua")
|
||||
|
||||
util.AddNetworkString("LEDDamaged")
|
||||
|
||||
function ENT:Initialize()
|
||||
|
||||
self:SetText("Made by Mac with <3")
|
||||
self:SetTColor(Vector(2.55,2,0))
|
||||
self:SetType(1)
|
||||
self:SetSpeed(1.5)
|
||||
self:SetWide(6)
|
||||
self:SetFX(1)
|
||||
self:SetOn(1)
|
||||
|
||||
self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMaterial("phoenix_storms/mat/mat_phx_carbonfiber")
|
||||
self:SetColor(Color(0,0,0))
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
self:SetUseType(SIMPLE_USE)
|
||||
self:SetNWInt("LastDamaged", 0)
|
||||
local phys = self:GetPhysicsObject()
|
||||
self.nodupe = true
|
||||
self.ShareGravgun = true
|
||||
|
||||
phys:Wake()
|
||||
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
|
||||
if self:GetModel() != "models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl" then
|
||||
self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl")
|
||||
end
|
||||
|
||||
self:NextThink( CurTime() + 1 )
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
function ENT:OnTakeDamage(data)
|
||||
|
||||
if data:GetDamagePosition() then
|
||||
self:EmitSound("ambient/energy/spark".. math.random(1,6) ..".wav")
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( data:GetDamagePosition() )
|
||||
util.Effect( "StunstickImpact", effectdata )
|
||||
self:SetNWInt("LastDamaged", math.Round(CurTime()+2))
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_gmodentity"
|
||||
ENT.PrintName = "Spawned Sign"
|
||||
ENT.Author = "Mac"
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = true
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:NetworkVar("String", 0, "Text")
|
||||
self:NetworkVar("Vector", 0, "TColor")
|
||||
self:NetworkVar("Int", 0, "Type")
|
||||
self:NetworkVar("Int", 1, "Speed")
|
||||
self:NetworkVar("Int", 2, "Wide")
|
||||
self:NetworkVar("Int", 3, "On")
|
||||
self:NetworkVar("Int", 4, "FX")
|
||||
end
|
||||
@@ -0,0 +1,142 @@
|
||||
include("shared.lua")
|
||||
|
||||
local font = "InfoRUS2"
|
||||
|
||||
local sizetable = {
|
||||
[3] = {350, 0.5},
|
||||
[4] = {470, -11.5},
|
||||
[5] = {590, -11.5},
|
||||
[6] = {710, 0.5},
|
||||
[7] = {830, 0.5},
|
||||
[8] = {950, 0.5},
|
||||
}
|
||||
|
||||
function ENT:Initialize()
|
||||
|
||||
self.OldWide = self:GetWide()
|
||||
|
||||
self.frame = vgui.Create( "DPanel" )
|
||||
self.frame:SetSize( sizetable[self:GetWide()][1], 120 )
|
||||
self.frame.Text = self:GetText()
|
||||
self.frame.Type = self:GetType()
|
||||
self.frame.col = self:GetTColor()
|
||||
self.frame.damage = 0
|
||||
self.frame.appr = nil
|
||||
self.frame.FX = self:GetFX()
|
||||
self.frame.On = self:GetOn()
|
||||
self.frame.alfa = 0
|
||||
self.frame.speed = self:GetSpeed()
|
||||
self.frame:SetPaintedManually( true )
|
||||
self.frame.Paint = function(self,w,h)
|
||||
|
||||
if self.On <= 0 then
|
||||
if self.alfa < 1 then return end
|
||||
self.alfa = Lerp(FrameTime() * 5,self.alfa,0)
|
||||
else
|
||||
if self.FX > 0 then
|
||||
self.alfa = math.random(100,220)
|
||||
else
|
||||
self.alfa = 255
|
||||
end
|
||||
end
|
||||
|
||||
surface.DisableClipping( false )
|
||||
surface.SetFont(font)
|
||||
local ww,hh = surface.GetTextSize(self.Text)
|
||||
local multiplier = self.speed * 100
|
||||
|
||||
self.static = false
|
||||
|
||||
if self.damage < CurTime() and self.On then
|
||||
if self.Type == 1 then
|
||||
|
||||
local xs = (math.fmod(SysTime() * multiplier,w+ww)) - ww
|
||||
|
||||
draw.DrawText(self.Text,font,xs,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
|
||||
elseif self.Type == 2 then
|
||||
|
||||
if !self.appr or self.appr > ww then
|
||||
self.appr = -w
|
||||
else
|
||||
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
|
||||
end
|
||||
|
||||
draw.DrawText(self.Text,font,self.appr * -1,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
|
||||
else
|
||||
if !self.appr then
|
||||
self.appr = 0
|
||||
end
|
||||
|
||||
if w > ww then
|
||||
if self.Type == 3 then
|
||||
if self.appr < w-ww and !self.refl then
|
||||
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
|
||||
else
|
||||
if self.appr <= 0 then
|
||||
self.refl = nil
|
||||
else
|
||||
self.refl = true
|
||||
self.appr = math.Approach(self.appr, 0, FrameTime() * multiplier)
|
||||
end
|
||||
end
|
||||
else
|
||||
self.static = true
|
||||
end
|
||||
else
|
||||
if self.appr > w-ww-50 and !self.refl then
|
||||
self.appr = math.Approach(self.appr, w-ww-50, FrameTime() * multiplier)
|
||||
else
|
||||
if self.appr >= 50 then
|
||||
self.refl = nil
|
||||
else
|
||||
self.refl = true
|
||||
self.appr = math.Approach(self.appr, 50, FrameTime() * multiplier)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.static then
|
||||
draw.DrawText(self.Text,font,w/2,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),1)
|
||||
else
|
||||
draw.DrawText(self.Text,font,self.appr,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
|
||||
end
|
||||
end
|
||||
else
|
||||
draw.DrawText(self.Text,font,math.random(0,w-ww),10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, math.random(0,255)),0)
|
||||
end
|
||||
surface.DisableClipping( true )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:Draw()
|
||||
|
||||
self:DrawModel()
|
||||
|
||||
if self.frame then
|
||||
self.frame.Text = self:GetText()
|
||||
self.frame.Type = self:GetType()
|
||||
self.frame.col = self:GetTColor()
|
||||
self.frame.FX = self:GetFX()
|
||||
self.frame.On = self:GetOn()
|
||||
self.frame.damage = self:GetNWInt("LastDamaged")
|
||||
self.frame.speed = self:GetSpeed()
|
||||
end
|
||||
|
||||
local Pos = self:GetPos()
|
||||
local Ang = self:GetAngles()
|
||||
local hight = 12
|
||||
|
||||
if self.OldWide != self:GetWide() then
|
||||
self.frame:SetSize( sizetable[self:GetWide()][1], 120 )
|
||||
self.OldWide = self:GetWide()
|
||||
end
|
||||
|
||||
if self:GetWide() == 3 then
|
||||
hight = 6
|
||||
end
|
||||
|
||||
cam.Start3D2D(Pos + Ang:Up() * 1.1 - Ang:Right() * hight + Ang:Forward() * sizetable[self:GetWide()][2], Ang, 0.1)
|
||||
self.frame:PaintManual()
|
||||
cam.End3D2D()
|
||||
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
AddCSLuaFile("cl_init.lua")
|
||||
AddCSLuaFile("shared.lua")
|
||||
include("shared.lua")
|
||||
|
||||
util.AddNetworkString("LEDDamaged")
|
||||
|
||||
function ENT:Initialize()
|
||||
|
||||
self:SetText("Made by Mac with Wire and <3")
|
||||
self:SetTColor(Vector(2.55,2,0))
|
||||
self:SetType(1)
|
||||
self:SetSpeed(1.5)
|
||||
self:SetWide(6)
|
||||
self:SetFX(1)
|
||||
self:SetOn(1)
|
||||
|
||||
self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMaterial("phoenix_storms/mat/mat_phx_carbonfiber")
|
||||
self:SetColor(Color(0,0,0))
|
||||
self:SetMoveType(MOVETYPE_VPHYSICS)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
self:SetUseType(SIMPLE_USE)
|
||||
self:SetNWInt("LastDamaged", 0)
|
||||
local phys = self:GetPhysicsObject()
|
||||
self.nodupe = true
|
||||
self.ShareGravgun = true
|
||||
|
||||
phys:Wake()
|
||||
|
||||
|
||||
self.Inputs = WireLib.CreateSpecialInputs(self, { "Text", "Type", "Speed", "Color", "Flicker", "On" }, { "STRING", "NORMAL", "NORMAL", "VECTOR", "NORMAL", "NORMAL" })
|
||||
|
||||
end
|
||||
|
||||
function ENT:TriggerInput(iname, value)
|
||||
if iname == "Text" then
|
||||
self:SetText(value)
|
||||
elseif iname == "Type" then
|
||||
self:SetType(math.Clamp(math.Round(value), 1, 4))
|
||||
elseif iname == "Speed" then
|
||||
self:SetSpeed(math.Clamp(value, 1, 10))
|
||||
elseif iname == "Color" then
|
||||
self:SetTColor(Vector(value.x/100, value.y/100, value.z/100))
|
||||
elseif iname == "On" then
|
||||
self:SetOn(value)
|
||||
elseif iname == "Flicker" then
|
||||
self:SetFX(math.Clamp(value, 0, 1))
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
|
||||
if self:GetModel() != "models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl" then
|
||||
self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function ENT:OnTakeDamage(data)
|
||||
|
||||
if data:GetDamagePosition() then
|
||||
self:EmitSound("ambient/energy/spark".. math.random(1,6) ..".wav")
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( data:GetDamagePosition() )
|
||||
util.Effect( "StunstickImpact", effectdata )
|
||||
self:SetNWInt("LastDamaged", math.Round(CurTime()+2))
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = "base_wire_entity"
|
||||
ENT.PrintName = "Spawned Sign Wire"
|
||||
ENT.WireDebugName = "LED Screen"
|
||||
ENT.Author = "Mac"
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = true
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:NetworkVar("String", 0, "Text")
|
||||
self:NetworkVar("Vector", 0, "TColor")
|
||||
self:NetworkVar("Int", 0, "Type")
|
||||
self:NetworkVar("Int", 1, "Speed")
|
||||
self:NetworkVar("Int", 2, "Wide")
|
||||
self:NetworkVar("Int", 3, "On")
|
||||
self:NetworkVar("Int", 4, "FX")
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
include( "shared.lua" )
|
||||
|
||||
function ENT:Draw()
|
||||
self.BaseClass.Draw(self)
|
||||
self.Entity:DrawModel()
|
||||
end
|
||||
@@ -0,0 +1,291 @@
|
||||
--[[
|
||||
Title: Adv. Dupe 2 Contraption Spawner
|
||||
|
||||
Desc: A mobile duplicator
|
||||
|
||||
Author: TB
|
||||
|
||||
Version: 1.0
|
||||
]]
|
||||
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
if(WireLib)then
|
||||
include( "entities/base_wire_entity.lua" )
|
||||
end
|
||||
|
||||
include( "shared.lua" )
|
||||
|
||||
function ENT:Initialize()
|
||||
|
||||
self.Entity:SetMoveType( MOVETYPE_NONE )
|
||||
self.Entity:PhysicsInit( SOLID_VPHYSICS )
|
||||
self.Entity:SetCollisionGroup( COLLISION_GROUP_WORLD )
|
||||
self.Entity:DrawShadow( false )
|
||||
|
||||
local phys = self.Entity:GetPhysicsObject()
|
||||
if phys:IsValid() then
|
||||
phys:Wake()
|
||||
end
|
||||
|
||||
self.UndoList = {}
|
||||
self.Ghosts = {}
|
||||
|
||||
self.SpawnLastValue = 0
|
||||
self.UndoLastValue = 0
|
||||
|
||||
self.LastSpawnTime = 0
|
||||
self.DupeName = ""
|
||||
self.CurrentPropCount = 0
|
||||
|
||||
if WireLib then
|
||||
self.Inputs = Wire_CreateInputs(self.Entity, {"Spawn", "Undo"})
|
||||
self.Outputs = WireLib.CreateSpecialOutputs(self.Entity, {"Out"}, { "NORMAL" })
|
||||
end
|
||||
end
|
||||
|
||||
/*-----------------------------------------------------------------------*
|
||||
* Sets options for this spawner
|
||||
*-----------------------------------------------------------------------*/
|
||||
function ENT:SetOptions(ply, delay, undo_delay, key, undo_key, disgrav, disdrag, addvel, hideprops )
|
||||
|
||||
self.delay = delay
|
||||
self.undo_delay = undo_delay
|
||||
|
||||
--Key bindings
|
||||
self.key = key
|
||||
self.undo_key = undo_key
|
||||
numpad.Remove( self.CreateKey )
|
||||
numpad.Remove( self.UndoKey )
|
||||
self.CreateKey = numpad.OnDown( ply, self.key , "ContrSpawnerCreate", self.Entity, true )
|
||||
self.UndoKey = numpad.OnDown( ply, self.undo_key, "ContrSpawnerUndo" , self.Entity, true )
|
||||
|
||||
-- Other parameters
|
||||
self.DisableGravity = disgrav
|
||||
self.DisableDrag = disdrag
|
||||
self.AddVelocity = addvel
|
||||
self.HideProps = hideprops
|
||||
|
||||
-- Store the player's current dupe name
|
||||
self.DupeName = tostring(ply.AdvDupe2.Name)
|
||||
|
||||
self:ShowOutput()
|
||||
end
|
||||
|
||||
function ENT:UpdateOptions( options )
|
||||
self:SetOptions( options["delay"], options["undo_delay"], options["key"], options["undo_key"])
|
||||
end
|
||||
|
||||
function ENT:AddGhosts()
|
||||
if self.HideProps then return end
|
||||
local moveable = self:GetPhysicsObject():IsMoveable()
|
||||
self:GetPhysicsObject():EnableMotion(false)
|
||||
local EntTable, GhostEntity, Phys
|
||||
local Offset = self.DupeAngle - self.EntAngle
|
||||
for EntIndex,v in pairs(self.EntityTable)do
|
||||
if(EntIndex!=self.HeadEnt)then
|
||||
if(self.EntityTable[EntIndex].Class=="gmod_contr_spawner")then self.EntityTable[EntIndex] = nil continue end
|
||||
EntTable = table.Copy(self.EntityTable[EntIndex])
|
||||
if(EntTable.BuildDupeInfo && EntTable.BuildDupeInfo.PhysicsObjects)then
|
||||
Phys = EntTable.BuildDupeInfo.PhysicsObjects[0]
|
||||
else
|
||||
if(!v.BuildDupeInfo)then v.BuildDupeInfo = {} end
|
||||
v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects)
|
||||
Phys = EntTable.PhysicsObjects[0]
|
||||
end
|
||||
|
||||
GhostEntity = nil
|
||||
|
||||
if(EntTable.Model==nil || !util.IsValidModel(EntTable.Model)) then EntTable.Model="models/error.mdl" end
|
||||
|
||||
if ( EntTable.Model:sub( 1, 1 ) == "*" ) then
|
||||
GhostEntity = ents.Create( "func_physbox" )
|
||||
else
|
||||
GhostEntity = ents.Create( "gmod_ghost" )
|
||||
end
|
||||
|
||||
// If there are too many entities we might not spawn..
|
||||
if ( !GhostEntity || GhostEntity == NULL ) then return end
|
||||
|
||||
duplicator.DoGeneric( GhostEntity, EntTable )
|
||||
|
||||
GhostEntity:Spawn()
|
||||
|
||||
GhostEntity:DrawShadow( false )
|
||||
GhostEntity:SetMoveType( MOVETYPE_NONE )
|
||||
GhostEntity:SetSolid( SOLID_VPHYSICS );
|
||||
GhostEntity:SetNotSolid( true )
|
||||
GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA )
|
||||
GhostEntity:SetColor( Color(255, 255, 255, 150) )
|
||||
|
||||
GhostEntity:SetAngles(Phys.Angle)
|
||||
GhostEntity:SetPos(self:GetPos() + Phys.Pos - self.Offset)
|
||||
self:SetAngles(self.EntAngle)
|
||||
GhostEntity:SetParent( self )
|
||||
self:SetAngles(self.DupeAngle)
|
||||
self.Ghosts[EntIndex] = GhostEntity
|
||||
end
|
||||
end
|
||||
self:SetAngles(self.DupeAngle)
|
||||
self:GetPhysicsObject():EnableMotion(moveable)
|
||||
end
|
||||
|
||||
function ENT:GetCreationDelay() return self.delay end
|
||||
function ENT:GetDeletionDelay() return self.undo_delay end
|
||||
|
||||
function ENT:OnTakeDamage( dmginfo ) self.Entity:TakePhysicsDamage( dmginfo ) end
|
||||
|
||||
function ENT:SetDupeInfo( HeadEnt, EntityTable, ConstraintTable )
|
||||
self.HeadEnt = HeadEnt
|
||||
self.EntityTable = EntityTable
|
||||
self.ConstraintTable = ConstraintTable
|
||||
if(!self.DupeAngle)then self.DupeAngle = self:GetAngles() end
|
||||
if(!self.EntAngle)then self.EntAngle = EntityTable[HeadEnt].PhysicsObjects[0].Angle end
|
||||
if(!self.Offset)then self.Offset = self.EntityTable[HeadEnt].PhysicsObjects[0].Pos end
|
||||
|
||||
local headpos, headang = EntityTable[HeadEnt].PhysicsObjects[0].Pos, EntityTable[HeadEnt].PhysicsObjects[0].Angle
|
||||
for k, v in pairs(EntityTable) do
|
||||
for o, p in pairs(v.PhysicsObjects) do
|
||||
p.LPos, p.LAngle = WorldToLocal(p.Pos, p.Angle, headpos, headang)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DoSpawn( ply )
|
||||
-- Explicitly allow spawning if no player is provided, but an invalid player gets denied. This can happen when a player leaves the server.
|
||||
if not (ply and ply:IsValid()) then return end
|
||||
|
||||
for k, v in pairs(self.EntityTable) do
|
||||
for o, p in pairs(v.PhysicsObjects) do
|
||||
p.Pos, p.Angle = self:LocalToWorld(p.LPos), self:LocalToWorldAngles(p.LAngle)
|
||||
end
|
||||
end
|
||||
|
||||
/*local AngleOffset = self.EntAngle
|
||||
AngleOffset = self:GetAngles() - AngleOffset
|
||||
local AngleOffset2 = Angle(0,0,0)
|
||||
//AngleOffset2.y = AngleOffset.y
|
||||
AngleOffset2:RotateAroundAxis(self:GetUp(), AngleOffset.y)
|
||||
AngleOffset2:RotateAroundAxis(self:GetRight(),AngleOffset.p)
|
||||
AngleOffset2:RotateAroundAxis(self:GetForward(),AngleOffset.r)*/
|
||||
|
||||
local Ents, Constrs = AdvDupe2.duplicator.Paste(ply, self.EntityTable, self.ConstraintTable, nil, nil, Vector(0,0,0), true)
|
||||
local i = #self.UndoList+1
|
||||
self.UndoList[i] = Ents
|
||||
|
||||
local undotxt = "AdvDupe2: Contraption ("..tostring(self.DupeName)..")"
|
||||
|
||||
undo.Create(undotxt)
|
||||
local phys
|
||||
for k,ent in pairs(Ents)do
|
||||
phys = ent:GetPhysicsObject()
|
||||
if IsValid(phys) then
|
||||
phys:Wake()
|
||||
if(self.DisableGravity==1)then phys:EnableGravity(false) end
|
||||
if(self.DisableDrag==1)then phys:EnableDrag(false) end
|
||||
phys:EnableMotion(true)
|
||||
if(ent.SetForce)then ent.SetForce(ent, ent.force, ent.mul) end
|
||||
if(self.AddVelocity==1)then
|
||||
phys:SetVelocity( self:GetVelocity() )
|
||||
phys:AddAngleVelocity( self:GetPhysicsObject():GetAngleVelocity() )
|
||||
end
|
||||
end
|
||||
|
||||
undo.AddEntity(ent)
|
||||
end
|
||||
|
||||
undo.SetPlayer(ply)
|
||||
undo.Finish()
|
||||
|
||||
if(self.undo_delay>0)then
|
||||
timer.Simple(self.undo_delay, function()
|
||||
if(self.UndoList && self.UndoList[i])then
|
||||
for k,ent in pairs(self.UndoList[i]) do
|
||||
if(IsValid(ent)) then
|
||||
ent:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DoUndo( ply )
|
||||
|
||||
if(!self.UndoList || #self.UndoList == 0)then return end
|
||||
|
||||
local entities = self.UndoList[ #self.UndoList ]
|
||||
self.UndoList[ #self.UndoList ] = nil
|
||||
for _,ent in pairs(entities) do
|
||||
if (IsValid(ent)) then
|
||||
ent:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:TriggerInput(iname, value)
|
||||
local ply = self:GetPlayer()
|
||||
|
||||
if(iname == "Spawn")then
|
||||
if ((value > 0) == self.SpawnLastValue) then return end
|
||||
self.SpawnLastValue = (value > 0)
|
||||
|
||||
if(self.SpawnLastValue)then
|
||||
local delay = self:GetCreationDelay()
|
||||
if (delay == 0) then self:DoSpawn( ply ) return end
|
||||
if(CurTime() < self.LastSpawnTime)then return end
|
||||
self:DoSpawn( ply )
|
||||
self.LastSpawnTime=CurTime()+delay
|
||||
end
|
||||
elseif (iname == "Undo") then
|
||||
// Same here
|
||||
if((value > 0) == self.UndoLastValue)then return end
|
||||
self.UndoLastValue = (value > 0)
|
||||
|
||||
if(self.UndoLastValue)then self:DoUndo(ply) end
|
||||
end
|
||||
end
|
||||
|
||||
local flags = {"Enabled", "Disabled"}
|
||||
function ENT:ShowOutput()
|
||||
local text = "\nGravity: "..((self.DisableGravity == 1) and flags[1] or flags[2])
|
||||
text = text.."\nDrag: " ..((self.DisableDrag == 1) and flags[1] or flags[2])
|
||||
text = text.."\nVelocity: "..((self.AddVelocity == 1) and flags[1] or flags[2])
|
||||
|
||||
self.Entity:SetOverlayText(
|
||||
"Spawn Name: " .. tostring(self.DupeName) ..
|
||||
"\nSpawn Delay: " .. tostring(self:GetCreationDelay()) ..
|
||||
"\nUndo Delay: ".. tostring(self:GetDeletionDelay()) ..
|
||||
text
|
||||
)
|
||||
end
|
||||
|
||||
/*-----------------------------------------------------------------------*
|
||||
* Handler for spawn keypad input
|
||||
*-----------------------------------------------------------------------*/
|
||||
function SpawnContrSpawner( ply, ent )
|
||||
|
||||
if (!ent || !ent:IsValid()) then return end
|
||||
|
||||
local delay = ent:GetTable():GetCreationDelay()
|
||||
|
||||
if(delay == 0) then
|
||||
ent:DoSpawn( ply )
|
||||
return
|
||||
end
|
||||
|
||||
if(CurTime() < ent.LastSpawnTime)then return end
|
||||
ent:DoSpawn( ply )
|
||||
ent.LastSpawnTime=CurTime()+delay
|
||||
end
|
||||
|
||||
/*-----------------------------------------------------------------------*
|
||||
* Handler for undo keypad input
|
||||
*-----------------------------------------------------------------------*/
|
||||
function UndoContrSpawner( ply, ent )
|
||||
if (!ent || !ent:IsValid()) then return end
|
||||
ent:DoUndo( ply, true )
|
||||
end
|
||||
|
||||
numpad.Register( "ContrSpawnerCreate", SpawnContrSpawner )
|
||||
numpad.Register( "ContrSpawnerUndo" , UndoContrSpawner )
|
||||
@@ -0,0 +1,10 @@
|
||||
ENT.Type = "anim"
|
||||
ENT.Base = WireLib and "base_wire_entity" or "base_gmodentity"
|
||||
ENT.PrintName = "Contraption Spawner"
|
||||
ENT.Author = "TB"
|
||||
ENT.Contact = ""
|
||||
ENT.Purpose = ""
|
||||
ENT.Instructions = ""
|
||||
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminSpawnable = false
|
||||
@@ -0,0 +1,141 @@
|
||||
AddCSLuaFile()
|
||||
|
||||
ENT.Type = "anim"
|
||||
|
||||
ENT.PrintName = ""
|
||||
ENT.Author = ""
|
||||
ENT.Contact = ""
|
||||
ENT.Purpose = ""
|
||||
ENT.Instructions = ""
|
||||
ENT.Spawnable = false
|
||||
ENT.AdminOnly = false
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Name: Initialize
|
||||
-----------------------------------------------------------]]
|
||||
function ENT:Initialize()
|
||||
|
||||
local Radius = 6
|
||||
local min = Vector( 1, 1, 1 ) * Radius * -0.5
|
||||
local max = Vector( 1, 1, 1 ) * Radius * 0.5
|
||||
|
||||
if ( SERVER ) then
|
||||
|
||||
self.AttachedEntity = ents.Create( "prop_dynamic" )
|
||||
self.AttachedEntity:SetModel( self:GetModel() )
|
||||
self.AttachedEntity:SetAngles( self:GetAngles() )
|
||||
self.AttachedEntity:SetPos( self:GetPos() )
|
||||
self.AttachedEntity:SetSkin( self:GetSkin() )
|
||||
self.AttachedEntity:Spawn()
|
||||
self.AttachedEntity:SetParent( self.Entity )
|
||||
self.AttachedEntity:DrawShadow( false )
|
||||
|
||||
self:SetModel( "models/props_junk/watermelon01.mdl" )
|
||||
|
||||
self:DeleteOnRemove( self.AttachedEntity )
|
||||
|
||||
-- Don't use the model's physics - create a box instead
|
||||
self:PhysicsInitBox( min, max )
|
||||
|
||||
-- Set up our physics object here
|
||||
local phys = self:GetPhysicsObject()
|
||||
if ( IsValid( phys ) ) then
|
||||
phys:Wake()
|
||||
phys:EnableGravity( false )
|
||||
phys:EnableDrag( false )
|
||||
end
|
||||
|
||||
self:DrawShadow( false )
|
||||
self:SetCollisionGroup( COLLISION_GROUP_WEAPON )
|
||||
|
||||
else
|
||||
|
||||
self.GripMaterial = Material( "sprites/grip" )
|
||||
|
||||
-- Get the attached entity so that clientside functions like properties can interact with it
|
||||
local tab = ents.FindByClassAndParent( "prop_dynamic", self )
|
||||
if ( tab && IsValid( tab[ 1 ] ) ) then self.AttachedEntity = tab[ 1 ] end
|
||||
|
||||
end
|
||||
|
||||
-- Set collision bounds exactly
|
||||
self:SetCollisionBounds( min, max )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Name: Draw
|
||||
-----------------------------------------------------------]]
|
||||
function ENT:Draw()
|
||||
|
||||
render.SetMaterial( self.GripMaterial )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Name: PhysicsUpdate
|
||||
-----------------------------------------------------------]]
|
||||
function ENT:PhysicsUpdate( physobj )
|
||||
|
||||
if ( CLIENT ) then return end
|
||||
|
||||
-- Don't do anything if the player isn't holding us
|
||||
if ( !self:IsPlayerHolding() && !self:IsConstrained() ) then
|
||||
|
||||
physobj:SetVelocity( Vector( 0, 0, 0 ) )
|
||||
physobj:Sleep()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Name: Called after entity 'copy'
|
||||
-----------------------------------------------------------]]
|
||||
function ENT:OnEntityCopyTableFinish( tab )
|
||||
|
||||
-- We need to store the model of the attached entity
|
||||
-- Not the one we have here.
|
||||
tab.Model = self.AttachedEntity:GetModel()
|
||||
|
||||
-- Store the attached entity's table so we can restore it after being pasted
|
||||
tab.AttachedEntityInfo = table.Copy( duplicator.CopyEntTable( self.AttachedEntity ) )
|
||||
tab.AttachedEntityInfo.Pos = nil -- Don't even save angles and position, we are a parented entity
|
||||
tab.AttachedEntityInfo.Angle = nil
|
||||
|
||||
-- Do NOT store the attached entity itself in our table!
|
||||
-- Otherwise, if we copy-paste the prop with the duplicator, its AttachedEntity value will point towards the original prop's attached entity instead, and that'll break stuff
|
||||
tab.AttachedEntity = nil
|
||||
|
||||
end
|
||||
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Name: PostEntityPaste
|
||||
-----------------------------------------------------------]]
|
||||
function ENT:PostEntityPaste( ply )
|
||||
|
||||
-- Restore the attached entity using the information we've saved
|
||||
if ( IsValid( self.AttachedEntity ) ) and ( self.AttachedEntityInfo ) then
|
||||
|
||||
-- Apply skin, bodygroups, bone manipulator, etc.
|
||||
duplicator.DoGeneric( self.AttachedEntity, self.AttachedEntityInfo )
|
||||
|
||||
if ( self.AttachedEntityInfo.EntityMods ) then
|
||||
self.AttachedEntity.EntityMods = table.Copy( self.AttachedEntityInfo.EntityMods )
|
||||
duplicator.ApplyEntityModifiers( ply, self.AttachedEntity )
|
||||
end
|
||||
|
||||
if ( self.AttachedEntityInfo.BoneMods ) then
|
||||
self.AttachedEntity.BoneMods = table.Copy( self.AttachedEntityInfo.BoneMods )
|
||||
duplicator.ApplyBoneModifiers( ply, self.AttachedEntity )
|
||||
end
|
||||
|
||||
self.AttachedEntityInfo = nil
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
386
garrysmod/gamemodes/militaryrp/plugins/buildtools/netstream.lua
Normal file
386
garrysmod/gamemodes/militaryrp/plugins/buildtools/netstream.lua
Normal file
@@ -0,0 +1,386 @@
|
||||
--A net extension which allows sending large streams of data without overflowing the reliable channel
|
||||
--Keep it in lua/autorun so it will be shared between addons
|
||||
AddCSLuaFile()
|
||||
|
||||
net.Stream = {}
|
||||
net.Stream.SendSize = 20000 --This is the size of each packet to send
|
||||
net.Stream.Timeout = 30 --How long to wait for client response before cleaning up
|
||||
net.Stream.MaxWriteStreams = 1024 --The maximum number of write data items to store
|
||||
net.Stream.MaxReadStreams = 128 --The maximum number of queued read data items to store
|
||||
net.Stream.MaxChunks = 3200 --Maximum number of pieces the stream can send to the server. 64 MB
|
||||
net.Stream.MaxSize = net.Stream.SendSize*net.Stream.MaxChunks
|
||||
net.Stream.MaxTries = 3 --Maximum times the client may retry downloading the whole data
|
||||
|
||||
local WriteStreamQueue = {
|
||||
__index = {
|
||||
Add = function(self, stream)
|
||||
local identifier = self.curidentifier
|
||||
local startid = identifier
|
||||
while self.queue[identifier] do
|
||||
identifier = identifier % net.Stream.MaxWriteStreams + 1
|
||||
if identifier == startid then
|
||||
ErrorNoHalt("Netstream is full of WriteStreams!")
|
||||
net.WriteUInt(0, 32)
|
||||
return
|
||||
end
|
||||
end
|
||||
self.curidentifier = identifier % net.Stream.MaxWriteStreams + 1
|
||||
|
||||
if next(self.queue)==nil then
|
||||
self.activitytimeout = CurTime()+net.Stream.Timeout
|
||||
timer.Create("netstream_queueclean", 5, 0, function() self:Clean() end)
|
||||
end
|
||||
self.queue[identifier] = stream
|
||||
stream.identifier = identifier
|
||||
return stream
|
||||
end,
|
||||
|
||||
Write = function(self, ply)
|
||||
local identifier = net.ReadUInt(32)
|
||||
local chunkidx = net.ReadUInt(32)
|
||||
local stream = self.queue[identifier]
|
||||
--print("Got request", identifier, chunkidx, stream)
|
||||
if stream then
|
||||
if stream:Write(ply, chunkidx) then
|
||||
self.activitytimeout = CurTime()+net.Stream.Timeout
|
||||
stream.timeout = CurTime()+net.Stream.Timeout
|
||||
end
|
||||
else
|
||||
-- Tell them the stream doesn't exist
|
||||
net.Start("NetStreamRead")
|
||||
net.WriteUInt(identifier, 32)
|
||||
net.WriteUInt(0, 32)
|
||||
if SERVER then net.Send(ply) else net.SendToServer() end
|
||||
end
|
||||
end,
|
||||
|
||||
Clean = function(self)
|
||||
local t = CurTime()
|
||||
for k, stream in pairs(self.queue) do
|
||||
if (next(stream.clients)~=nil and t >= stream.timeout) or t >= self.activitytimeout then
|
||||
stream:Remove()
|
||||
self.queue[k] = nil
|
||||
end
|
||||
end
|
||||
if next(self.queue)==nil then
|
||||
timer.Remove("netstream_queueclean")
|
||||
end
|
||||
end,
|
||||
},
|
||||
__call = function(t)
|
||||
return setmetatable({
|
||||
activitytimeout = CurTime()+net.Stream.Timeout,
|
||||
curidentifier = 1,
|
||||
queue = {}
|
||||
}, t)
|
||||
end
|
||||
}
|
||||
setmetatable(WriteStreamQueue, WriteStreamQueue)
|
||||
net.Stream.WriteStreams = WriteStreamQueue()
|
||||
|
||||
local ReadStreamQueue = {
|
||||
__index = {
|
||||
Add = function(self, stream)
|
||||
local queue = self.queues[stream.player]
|
||||
|
||||
if #queue == net.Stream.MaxReadStreams then
|
||||
ErrorNoHalt("Receiving too many ReadStream requests!")
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in ipairs(queue) do
|
||||
if v.identifier == stream.identifier then
|
||||
ErrorNoHalt("Tried to start a new ReadStream for an already existing stream!")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
queue[#queue+1] = stream
|
||||
if #queue == 1 then
|
||||
stream:Request()
|
||||
end
|
||||
return stream
|
||||
end,
|
||||
|
||||
Remove = function(self, stream)
|
||||
local queue = rawget(self.queues, stream.player)
|
||||
if queue then
|
||||
if stream == queue[1] then
|
||||
table.remove(queue, 1)
|
||||
local nextInQueue = queue[1]
|
||||
if nextInQueue then
|
||||
nextInQueue:Request()
|
||||
else
|
||||
self.queues[stream.player] = nil
|
||||
end
|
||||
else
|
||||
for k, v in ipairs(queue) do
|
||||
if v == stream then
|
||||
table.remove(queue, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
Read = function(self, ply)
|
||||
local identifier = net.ReadUInt(32)
|
||||
local queue = rawget(self.queues, ply)
|
||||
if queue and queue[1] then
|
||||
queue[1]:Read(identifier)
|
||||
end
|
||||
end
|
||||
},
|
||||
__call = function(t)
|
||||
return setmetatable({
|
||||
queues = setmetatable({}, {__index = function(t,k) local r={} t[k]=r return r end})
|
||||
}, t)
|
||||
end
|
||||
}
|
||||
setmetatable(ReadStreamQueue, ReadStreamQueue)
|
||||
net.Stream.ReadStreams = ReadStreamQueue()
|
||||
|
||||
|
||||
local WritingDataItem = {
|
||||
__index = {
|
||||
Write = function(self, ply, chunkidx)
|
||||
local client = self.clients[ply]
|
||||
if client.finished then return false end
|
||||
if chunkidx == #self.chunks+1 then self:Finished(ply) return true end
|
||||
|
||||
if client.downloads+#self.chunks-client.progress >= net.Stream.MaxTries * #self.chunks then self:Finished(ply) return false end
|
||||
client.downloads = client.downloads + 1
|
||||
|
||||
local chunk = self.chunks[chunkidx]
|
||||
if not chunk then return false end
|
||||
|
||||
client.progress = chunkidx
|
||||
|
||||
--print("Sending", "NetStreamRead", self.identifier, #chunk.data, chunkidx, chunk.crc)
|
||||
net.Start("NetStreamRead")
|
||||
net.WriteUInt(self.identifier, 32)
|
||||
net.WriteUInt(#chunk.data, 32)
|
||||
net.WriteUInt(chunkidx, 32)
|
||||
net.WriteString(chunk.crc)
|
||||
net.WriteData(chunk.data, #chunk.data)
|
||||
if CLIENT then net.SendToServer() else net.Send(ply) end
|
||||
return true
|
||||
end,
|
||||
|
||||
Finished = function(self, ply)
|
||||
self.clients[ply].finished = true
|
||||
if self.callback then
|
||||
local ok, err = xpcall(self.callback, debug.traceback, ply)
|
||||
if not ok then ErrorNoHalt(err) end
|
||||
end
|
||||
end,
|
||||
|
||||
GetProgress = function(self, ply)
|
||||
return self.clients[ply].progress / #self.chunks
|
||||
end,
|
||||
|
||||
Remove = function(self)
|
||||
local sendTo = {}
|
||||
for ply, client in pairs(self.clients) do
|
||||
if not client.finished then
|
||||
client.finished = true
|
||||
if CLIENT or ply:IsValid() then sendTo[#sendTo+1] = ply end
|
||||
end
|
||||
end
|
||||
|
||||
if next(sendTo)~=nil then
|
||||
--print("Sending", "NetStreamRead", self.identifier, 0)
|
||||
net.Start("NetStreamRead")
|
||||
net.WriteUInt(self.identifier, 32)
|
||||
net.WriteUInt(0, 32)
|
||||
if SERVER then net.Send(sendTo) else net.SendToServer() end
|
||||
end
|
||||
end
|
||||
|
||||
},
|
||||
__call = function(t, data, callback)
|
||||
local chunks = {}
|
||||
for i=1, math.ceil(#data / net.Stream.SendSize) do
|
||||
local datachunk = string.sub(data, (i - 1) * net.Stream.SendSize + 1, i * net.Stream.SendSize)
|
||||
chunks[i] = { data = datachunk, crc = util.CRC(datachunk) }
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
timeout = CurTime()+net.Stream.Timeout,
|
||||
chunks = chunks,
|
||||
callback = callback,
|
||||
lasttouched = 0,
|
||||
clients = setmetatable({},{__index = function(t,k)
|
||||
local r = {
|
||||
finished = false,
|
||||
downloads = 0,
|
||||
progress = 0,
|
||||
} t[k]=r return r
|
||||
end})
|
||||
}, t)
|
||||
end
|
||||
}
|
||||
setmetatable(WritingDataItem, WritingDataItem)
|
||||
|
||||
local ReadingDataItem = {
|
||||
__index = {
|
||||
Request = function(self)
|
||||
if self.downloads+self.numchunks-#self.chunks >= net.Stream.MaxTries*self.numchunks then self:Remove() return end
|
||||
self.downloads = self.downloads + 1
|
||||
timer.Create("NetStreamReadTimeout" .. self.identifier, net.Stream.Timeout*0.5, 1, function() self:Request() end)
|
||||
self:WriteRequest()
|
||||
end,
|
||||
|
||||
WriteRequest = function(self)
|
||||
--print("Requesting", self.identifier, #self.chunks)
|
||||
net.Start("NetStreamWrite")
|
||||
net.WriteUInt(self.identifier, 32)
|
||||
net.WriteUInt(#self.chunks+1, 32)
|
||||
if CLIENT then net.SendToServer() else net.Send(self.player) end
|
||||
end,
|
||||
|
||||
Read = function(self, identifier)
|
||||
if self.identifier ~= identifier then self:Request() return end
|
||||
|
||||
local size = net.ReadUInt(32)
|
||||
if size == 0 then self:Remove() return end
|
||||
|
||||
local chunkidx = net.ReadUInt(32)
|
||||
if chunkidx ~= #self.chunks+1 then self:Request() return end
|
||||
|
||||
local crc = net.ReadString()
|
||||
local data = net.ReadData(size)
|
||||
|
||||
if crc ~= util.CRC(data) then self:Request() return end
|
||||
|
||||
self.chunks[chunkidx] = data
|
||||
if #self.chunks == self.numchunks then self:Remove(true) return end
|
||||
|
||||
self:Request()
|
||||
end,
|
||||
|
||||
GetProgress = function(self)
|
||||
return #self.chunks/self.numchunks
|
||||
end,
|
||||
|
||||
Remove = function(self, finished)
|
||||
timer.Remove("NetStreamReadTimeout" .. self.identifier)
|
||||
|
||||
local data
|
||||
if finished then
|
||||
data = table.concat(self.chunks)
|
||||
if self.compressed then
|
||||
data = util.Decompress(data, net.Stream.MaxSize)
|
||||
end
|
||||
self:WriteRequest() -- Notify we finished
|
||||
end
|
||||
|
||||
local ok, err = xpcall(self.callback, debug.traceback, data)
|
||||
if not ok then ErrorNoHalt(err) end
|
||||
|
||||
net.Stream.ReadStreams:Remove(self)
|
||||
end
|
||||
},
|
||||
__call = function(t, ply, callback, numchunks, identifier, compressed)
|
||||
return setmetatable({
|
||||
identifier = identifier,
|
||||
chunks = {},
|
||||
compressed = compressed,
|
||||
numchunks = numchunks,
|
||||
callback = callback,
|
||||
player = ply,
|
||||
downloads = 0
|
||||
}, t)
|
||||
end
|
||||
}
|
||||
setmetatable(ReadingDataItem, ReadingDataItem)
|
||||
|
||||
|
||||
function net.WriteStream(data, callback, dontcompress)
|
||||
if not isstring(data) then
|
||||
error("bad argument #1 to 'WriteStream' (string expected, got " .. type(data) .. ")", 2)
|
||||
end
|
||||
if callback ~= nil and not isfunction(callback) then
|
||||
error("bad argument #2 to 'WriteStream' (function expected, got " .. type(callback) .. ")", 2)
|
||||
end
|
||||
|
||||
local compressed = not dontcompress
|
||||
if compressed then
|
||||
data = util.Compress(data) or ""
|
||||
end
|
||||
|
||||
if #data == 0 then
|
||||
net.WriteUInt(0, 32)
|
||||
return
|
||||
end
|
||||
|
||||
if #data > net.Stream.MaxSize then
|
||||
ErrorNoHalt("net.WriteStream request is too large! ", #data/1048576, "MiB")
|
||||
net.WriteUInt(0, 32)
|
||||
return
|
||||
end
|
||||
|
||||
local stream = net.Stream.WriteStreams:Add(WritingDataItem(data, callback, compressed))
|
||||
if not stream then return end
|
||||
|
||||
--print("WriteStream", #stream.chunks, stream.identifier, compressed)
|
||||
net.WriteUInt(#stream.chunks, 32)
|
||||
net.WriteUInt(stream.identifier, 32)
|
||||
net.WriteBool(compressed)
|
||||
|
||||
return stream
|
||||
end
|
||||
|
||||
--If the receiver is a player then add it to a queue.
|
||||
--If the receiver is the server then add it to a queue for each individual player
|
||||
function net.ReadStream(ply, callback)
|
||||
if CLIENT then
|
||||
ply = NULL
|
||||
else
|
||||
if type(ply) ~= "Player" then
|
||||
error("bad argument #1 to 'ReadStream' (Player expected, got " .. type(ply) .. ")", 2)
|
||||
elseif not ply:IsValid() then
|
||||
error("bad argument #1 to 'ReadStream' (Tried to use a NULL entity!)", 2)
|
||||
end
|
||||
end
|
||||
if not isfunction(callback) then
|
||||
error("bad argument #2 to 'ReadStream' (function expected, got " .. type(callback) .. ")", 2)
|
||||
end
|
||||
|
||||
local numchunks = net.ReadUInt(32)
|
||||
if numchunks == nil then
|
||||
return
|
||||
elseif numchunks == 0 then
|
||||
local ok, err = xpcall(callback, debug.traceback, "")
|
||||
if not ok then ErrorNoHalt(err) end
|
||||
return
|
||||
end
|
||||
|
||||
local identifier = net.ReadUInt(32)
|
||||
local compressed = net.ReadBool()
|
||||
|
||||
if numchunks > net.Stream.MaxChunks then
|
||||
ErrorNoHalt("ReadStream requests from ", ply, " is too large! ", numchunks * net.Stream.SendSize / 1048576, "MiB")
|
||||
return
|
||||
end
|
||||
|
||||
--print("ReadStream", numchunks, identifier, compressed)
|
||||
|
||||
return net.Stream.ReadStreams:Add(ReadingDataItem(ply, callback, numchunks, identifier, compressed))
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
util.AddNetworkString("NetStreamWrite")
|
||||
util.AddNetworkString("NetStreamRead")
|
||||
end
|
||||
|
||||
--Send requested stream data
|
||||
net.Receive("NetStreamWrite", function(len, ply)
|
||||
net.Stream.WriteStreams:Write(ply or NULL)
|
||||
end)
|
||||
|
||||
--Download the sent stream data
|
||||
net.Receive("NetStreamRead", function(len, ply)
|
||||
net.Stream.ReadStreams:Read(ply or NULL)
|
||||
end)
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
____ _ _ ____ __ __ _ _
|
||||
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
|
||||
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
|
||||
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
|
||||
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|
||||
|___/
|
||||
*/
|
||||
|
||||
local function PermaPropsViewer()
|
||||
|
||||
if not LocalPlayer().DrawPPEnt or not istable(LocalPlayer().DrawPPEnt) then return end
|
||||
|
||||
local pos = LocalPlayer():EyePos() + LocalPlayer():EyeAngles():Forward() * 10
|
||||
local ang = LocalPlayer():EyeAngles()
|
||||
|
||||
ang = Angle(ang.p + 90, ang.y, 0)
|
||||
|
||||
for k, v in pairs(LocalPlayer().DrawPPEnt) do
|
||||
|
||||
if not v or not v:IsValid() then LocalPlayer().DrawPPEnt[k] = nil continue end
|
||||
|
||||
render.ClearStencil()
|
||||
render.SetStencilEnable(true)
|
||||
render.SetStencilWriteMask(255)
|
||||
render.SetStencilTestMask(255)
|
||||
render.SetStencilReferenceValue(15)
|
||||
render.SetStencilFailOperation(STENCILOPERATION_REPLACE)
|
||||
render.SetStencilZFailOperation(STENCILOPERATION_REPLACE)
|
||||
render.SetStencilPassOperation(STENCILOPERATION_KEEP)
|
||||
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS)
|
||||
render.SetBlend(0)
|
||||
v:DrawModel()
|
||||
render.SetBlend(1)
|
||||
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL)
|
||||
cam.Start3D2D(pos, ang, 1)
|
||||
surface.SetDrawColor(255, 0, 0, 255)
|
||||
surface.DrawRect(-ScrW(), -ScrH(), ScrW() * 2, ScrH() * 2)
|
||||
cam.End3D2D()
|
||||
v:DrawModel()
|
||||
render.SetStencilEnable(false)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
hook.Add("PostDrawOpaqueRenderables", "PermaPropsViewer", PermaPropsViewer)
|
||||
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
____ _ _ ____ __ __ _ _
|
||||
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
|
||||
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
|
||||
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
|
||||
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|
||||
|___/
|
||||
*/
|
||||
|
||||
surface.CreateFont( "pp_font", {
|
||||
font = "Arial",
|
||||
size = 20,
|
||||
weight = 700,
|
||||
shadow = false
|
||||
} )
|
||||
|
||||
local function pp_open_menu()
|
||||
|
||||
local Len = net.ReadFloat()
|
||||
local Data = net.ReadData( Len )
|
||||
local UnCompress = util.Decompress( Data )
|
||||
local Content = util.JSONToTable( UnCompress )
|
||||
|
||||
local Main = vgui.Create( "DFrame" )
|
||||
Main:SetSize( 600, 355 )
|
||||
Main:Center()
|
||||
Main:SetTitle("")
|
||||
Main:SetVisible( true )
|
||||
Main:SetDraggable( true )
|
||||
Main:ShowCloseButton( true )
|
||||
Main:MakePopup()
|
||||
Main.Paint = function(self)
|
||||
|
||||
draw.RoundedBox( 0, 0, 0, self:GetWide(), self:GetTall(), Color(155, 155, 155, 220) )
|
||||
surface.SetDrawColor( 17, 148, 240, 255 )
|
||||
surface.DrawOutlinedRect( 0, 0, self:GetWide(), self:GetTall() )
|
||||
|
||||
draw.RoundedBox( 0, 0, 0, self:GetWide(), 25, Color(17, 148, 240, 200) )
|
||||
surface.SetDrawColor( 17, 148, 240, 255 )
|
||||
surface.DrawOutlinedRect( 0, 0, self:GetWide(), 25 )
|
||||
draw.DrawText( "PermaProps Config", "pp_font", 10, 2.2, Color(255, 255, 255, 255), TEXT_ALIGN_LEFT )
|
||||
|
||||
end
|
||||
|
||||
local BSelect
|
||||
local PSelect
|
||||
|
||||
local MainPanel = vgui.Create( "DPanel", Main )
|
||||
MainPanel:SetPos( 190, 51 )
|
||||
MainPanel:SetSize( 390, 275 )
|
||||
MainPanel.Paint = function( self )
|
||||
surface.SetDrawColor( 50, 50, 50, 200 )
|
||||
surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() )
|
||||
surface.DrawOutlinedRect(0, 15, self:GetWide(), 40)
|
||||
end
|
||||
PSelect = MainPanel
|
||||
|
||||
local MainLabel = vgui.Create("DLabel", MainPanel)
|
||||
MainLabel:SetFont("pp_font")
|
||||
MainLabel:SetPos(140, 25)
|
||||
MainLabel:SetColor(Color(50, 50, 50, 255))
|
||||
MainLabel:SetText("Hey ".. LocalPlayer():Nick() .." !")
|
||||
MainLabel:SizeToContents()
|
||||
|
||||
local MainLabel2 = vgui.Create("DLabel", MainPanel)
|
||||
MainLabel2:SetFont("pp_font")
|
||||
MainLabel2:SetPos(80, 80)
|
||||
MainLabel2:SetColor(Color(50, 50, 50, 255))
|
||||
MainLabel2:SetText("There are ".. ( Content.MProps or 0 ) .." props on this map.\n\nThere are ".. ( Content.TProps or 0 ) .." props in the DB.")
|
||||
MainLabel2:SizeToContents()
|
||||
|
||||
local RemoveMapProps = vgui.Create( "DButton", MainPanel )
|
||||
RemoveMapProps:SetText( " Clear map props " )
|
||||
RemoveMapProps:SetFont("pp_font")
|
||||
RemoveMapProps:SetSize( 370, 30)
|
||||
RemoveMapProps:SetPos( 10, 160 )
|
||||
RemoveMapProps:SetTextColor( Color( 50, 50, 50, 255 ) )
|
||||
RemoveMapProps.DoClick = function()
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "CLR_MAP"})
|
||||
net.SendToServer()
|
||||
end
|
||||
RemoveMapProps.Paint = function(self)
|
||||
surface.SetDrawColor(50, 50, 50, 255)
|
||||
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
|
||||
end
|
||||
|
||||
local ClearMapProps = vgui.Create( "DButton", MainPanel )
|
||||
ClearMapProps:SetText( " Clear map props in the DB " )
|
||||
ClearMapProps:SetFont("pp_font")
|
||||
ClearMapProps:SetSize( 370, 30)
|
||||
ClearMapProps:SetPos( 10, 200 )
|
||||
ClearMapProps:SetTextColor( Color( 50, 50, 50, 255 ) )
|
||||
ClearMapProps.DoClick = function()
|
||||
|
||||
Derma_Query("Are you sure you want clear map props in the db ?\nYou can't undo this action !", "PermaProps 4.0", "Yes", function() net.Start("pp_info_send") net.WriteTable({CMD = "DEL_MAP"}) net.SendToServer() end, "Cancel")
|
||||
|
||||
end
|
||||
ClearMapProps.Paint = function(self)
|
||||
surface.SetDrawColor(50, 50, 50, 255)
|
||||
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
|
||||
end
|
||||
|
||||
local ClearAllProps = vgui.Create( "DButton", MainPanel )
|
||||
ClearAllProps:SetText( " Clear all props in the DB " )
|
||||
ClearAllProps:SetFont("pp_font")
|
||||
ClearAllProps:SetSize( 370, 30)
|
||||
ClearAllProps:SetPos( 10, 240 )
|
||||
ClearAllProps:SetTextColor( Color( 50, 50, 50, 255 ) )
|
||||
ClearAllProps.DoClick = function()
|
||||
|
||||
Derma_Query("Are you sure you want clear all props in the db ?\nYou can't undo this action !", "PermaProps 4.0", "Yes", function() net.Start("pp_info_send") net.WriteTable({CMD = "DEL_ALL"}) net.SendToServer() end, "Cancel")
|
||||
|
||||
end
|
||||
ClearAllProps.Paint = function(self)
|
||||
surface.SetDrawColor(50, 50, 50, 255)
|
||||
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
|
||||
end
|
||||
|
||||
local BMain = vgui.Create("DButton", Main)
|
||||
BSelect = BMain
|
||||
BMain:SetText("Main")
|
||||
BMain:SetFont("pp_font")
|
||||
BMain:SetSize(160, 50)
|
||||
BMain:SetPos(15, 27 + 25)
|
||||
BMain:SetTextColor( Color( 255, 255, 255, 255 ) )
|
||||
BMain.PaintColor = Color(17, 148, 240, 100)
|
||||
BMain.Paint = function(self)
|
||||
|
||||
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor)
|
||||
surface.SetDrawColor(17, 148, 240, 255)
|
||||
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
|
||||
|
||||
end
|
||||
BMain.DoClick = function( self )
|
||||
|
||||
if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end
|
||||
BSelect = self
|
||||
self.PaintColor = Color(17, 148, 240, 100)
|
||||
|
||||
if PSelect then PSelect:Hide() end
|
||||
MainPanel:Show()
|
||||
PSelect = MainPanel
|
||||
|
||||
end
|
||||
|
||||
local ConfigPanel = vgui.Create( "DPanel", Main )
|
||||
ConfigPanel:SetPos( 190, 51 )
|
||||
ConfigPanel:SetSize( 390, 275 )
|
||||
ConfigPanel.Paint = function( self )
|
||||
surface.SetDrawColor( 50, 50, 50, 200 )
|
||||
surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() )
|
||||
end
|
||||
ConfigPanel:Hide()
|
||||
|
||||
local CheckCustom = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckCustom:SetPos( 5, 30 )
|
||||
CheckCustom:SetText( "Custom permissions" )
|
||||
CheckCustom:SetValue( 0 )
|
||||
CheckCustom:SizeToContents()
|
||||
CheckCustom:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckCustom:SetDisabled( true )
|
||||
|
||||
local GroupsList = vgui.Create( "DComboBox", ConfigPanel )
|
||||
GroupsList:SetPos( 5, 5 )
|
||||
GroupsList:SetSize( 125, 20 )
|
||||
GroupsList:SetValue( "Select a group..." )
|
||||
|
||||
local CheckBox1 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckBox1:SetPos( 150, 10 )
|
||||
CheckBox1:SetText( "Menu" )
|
||||
CheckBox1:SizeToContents()
|
||||
CheckBox1:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckBox1:SetDisabled( true )
|
||||
CheckBox1.OnChange = function(Self, Value)
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Menu", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
local CheckBox2 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckBox2:SetPos( 150, 30 )
|
||||
CheckBox2:SetText( "Edit permissions" )
|
||||
CheckBox2:SizeToContents()
|
||||
CheckBox2:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckBox2:SetDisabled( true )
|
||||
CheckBox2.OnChange = function(Self, Value)
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Permissions", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
local CheckBox3 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckBox3:SetPos( 150, 50 )
|
||||
CheckBox3:SetText( "Physgun permaprops" )
|
||||
CheckBox3:SizeToContents()
|
||||
CheckBox3:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckBox3:SetDisabled( true )
|
||||
CheckBox3.OnChange = function(Self, Value)
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Physgun", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
local CheckBox4 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckBox4:SetPos( 150, 70 )
|
||||
CheckBox4:SetText( "Tool permaprops" )
|
||||
CheckBox4:SizeToContents()
|
||||
CheckBox4:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckBox4:SetDisabled( true )
|
||||
CheckBox4.OnChange = function(Self, Value)
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Tool", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
local CheckBox5 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckBox5:SetPos( 150, 90 )
|
||||
CheckBox5:SetText( "Property permaprops" )
|
||||
CheckBox5:SizeToContents()
|
||||
CheckBox5:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckBox5:SetDisabled( true )
|
||||
CheckBox5.OnChange = function(Self, Value)
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Property", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
local CheckBox6 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckBox6:SetPos( 150, 110 )
|
||||
CheckBox6:SetText( "Save props" )
|
||||
CheckBox6:SizeToContents()
|
||||
CheckBox6:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckBox6:SetDisabled( true )
|
||||
CheckBox6.OnChange = function(Self, Value)
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Save", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
local CheckBox7 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckBox7:SetPos( 150, 130 )
|
||||
CheckBox7:SetText( "Delete permaprops" )
|
||||
CheckBox7:SizeToContents()
|
||||
CheckBox7:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckBox7:SetDisabled( true )
|
||||
CheckBox7.OnChange = function(Self, Value)
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Delete", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
local CheckBox8 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
|
||||
CheckBox8:SetPos( 150, 150 )
|
||||
CheckBox8:SetText( "Update permaprops" )
|
||||
CheckBox8:SizeToContents()
|
||||
CheckBox8:SetTextColor( Color( 0, 0, 0, 255) )
|
||||
CheckBox8:SetDisabled( true )
|
||||
CheckBox8.OnChange = function(Self, Value)
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Update", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
GroupsList.OnSelect = function( panel, index, value )
|
||||
|
||||
CheckCustom:SetDisabled( false )
|
||||
CheckCustom:SetChecked( Content.Permissions[value].Custom )
|
||||
|
||||
CheckBox1:SetDisabled( !Content.Permissions[value].Custom )
|
||||
CheckBox1:SetChecked( Content.Permissions[value].Menu )
|
||||
CheckBox2:SetDisabled( !Content.Permissions[value].Custom )
|
||||
CheckBox2:SetChecked( Content.Permissions[value].Permissions )
|
||||
CheckBox3:SetDisabled( !Content.Permissions[value].Custom )
|
||||
CheckBox3:SetChecked( Content.Permissions[value].Physgun )
|
||||
CheckBox4:SetDisabled( !Content.Permissions[value].Custom )
|
||||
CheckBox4:SetChecked( Content.Permissions[value].Tool )
|
||||
CheckBox5:SetDisabled( !Content.Permissions[value].Custom )
|
||||
CheckBox5:SetChecked( Content.Permissions[value].Property )
|
||||
CheckBox6:SetDisabled( !Content.Permissions[value].Custom )
|
||||
CheckBox6:SetChecked( Content.Permissions[value].Save )
|
||||
CheckBox7:SetDisabled( !Content.Permissions[value].Custom )
|
||||
CheckBox7:SetChecked( Content.Permissions[value].Delete )
|
||||
CheckBox8:SetDisabled( !Content.Permissions[value].Custom )
|
||||
CheckBox8:SetChecked( Content.Permissions[value].Update )
|
||||
|
||||
end
|
||||
|
||||
for k, v in pairs(Content.Permissions) do
|
||||
|
||||
GroupsList:AddChoice(k)
|
||||
|
||||
end
|
||||
|
||||
CheckCustom.OnChange = function(Self, Value)
|
||||
|
||||
CheckBox1:SetDisabled( !Value )
|
||||
CheckBox2:SetDisabled( !Value )
|
||||
CheckBox3:SetDisabled( !Value )
|
||||
CheckBox4:SetDisabled( !Value )
|
||||
CheckBox5:SetDisabled( !Value )
|
||||
CheckBox6:SetDisabled( !Value )
|
||||
CheckBox7:SetDisabled( !Value )
|
||||
CheckBox8:SetDisabled( !Value )
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "VAR", Val = Value, Data = "Custom", Name = GroupsList:GetValue()})
|
||||
net.SendToServer()
|
||||
|
||||
end
|
||||
|
||||
local BConfig = vgui.Create("DButton", Main)
|
||||
BConfig:SetText("Configuration")
|
||||
BConfig:SetFont("pp_font")
|
||||
BConfig:SetSize(160, 50)
|
||||
BConfig:SetPos(15, 71 + 55)
|
||||
BConfig:SetTextColor( Color( 255, 255, 255, 255 ) )
|
||||
BConfig.PaintColor = Color(0, 0, 0, 0)
|
||||
BConfig.Paint = function(self)
|
||||
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor)
|
||||
surface.SetDrawColor(17, 148, 240, 255)
|
||||
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
|
||||
end
|
||||
BConfig.DoClick = function( self )
|
||||
|
||||
if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end
|
||||
BSelect = self
|
||||
self.PaintColor = Color(17, 148, 240, 100)
|
||||
|
||||
if PSelect then PSelect:Hide() end
|
||||
ConfigPanel:Show()
|
||||
PSelect = ConfigPanel
|
||||
|
||||
end
|
||||
|
||||
local PropsPanel = vgui.Create( "DPanel", Main )
|
||||
PropsPanel:SetPos( 190, 51 )
|
||||
PropsPanel:SetSize( 390, 275 )
|
||||
PropsPanel.Paint = function( self )
|
||||
surface.SetDrawColor( 50, 50, 50, 200 )
|
||||
surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() )
|
||||
end
|
||||
PropsPanel:Hide()
|
||||
|
||||
local PropsList = vgui.Create( "DListView", PropsPanel )
|
||||
PropsList:SetMultiSelect( false )
|
||||
PropsList:SetSize( 390, 275 )
|
||||
local ColID = PropsList:AddColumn( "ID" )
|
||||
local ColEnt = PropsList:AddColumn( "Entity" )
|
||||
local ColMdl = PropsList:AddColumn( "Model" )
|
||||
ColID:SetMinWidth(50)
|
||||
ColID:SetMaxWidth(50)
|
||||
PropsList.Paint = function( self )
|
||||
surface.SetDrawColor(17, 148, 240, 255)
|
||||
end
|
||||
|
||||
PropsList.OnRowRightClick = function(panel, line)
|
||||
|
||||
local MenuButtonOptions = DermaMenu()
|
||||
MenuButtonOptions:AddOption("Draw entity", function()
|
||||
|
||||
if not LocalPlayer().DrawPPEnt or not istable(LocalPlayer().DrawPPEnt) then LocalPlayer().DrawPPEnt = {} end
|
||||
|
||||
if LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:IsValid() then return end
|
||||
|
||||
local ent = ents.CreateClientProp( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Model )
|
||||
ent:SetPos( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Pos )
|
||||
ent:SetAngles( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Angle )
|
||||
|
||||
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = ent
|
||||
|
||||
end )
|
||||
|
||||
if LocalPlayer().DrawPPEnt and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] then
|
||||
|
||||
MenuButtonOptions:AddOption("Stop Drawing", function()
|
||||
|
||||
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:Remove()
|
||||
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = nil
|
||||
|
||||
end )
|
||||
|
||||
end
|
||||
|
||||
if LocalPlayer().DrawPPEnt != nil and istable(LocalPlayer().DrawPPEnt) and table.Count(LocalPlayer().DrawPPEnt) > 0 then
|
||||
|
||||
MenuButtonOptions:AddOption("Stop Drawing All", function()
|
||||
|
||||
for k, v in pairs(LocalPlayer().DrawPPEnt) do
|
||||
|
||||
LocalPlayer().DrawPPEnt[k]:Remove()
|
||||
LocalPlayer().DrawPPEnt[k] = nil
|
||||
|
||||
end
|
||||
|
||||
end )
|
||||
|
||||
end
|
||||
|
||||
MenuButtonOptions:AddOption("Remove", function()
|
||||
|
||||
net.Start("pp_info_send")
|
||||
net.WriteTable({CMD = "DEL", Val = PropsList:GetLine(line):GetValue(1)})
|
||||
net.SendToServer()
|
||||
|
||||
if LocalPlayer().DrawPPEnt and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] != nil then
|
||||
|
||||
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:Remove()
|
||||
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = nil
|
||||
|
||||
end
|
||||
|
||||
PropsList:RemoveLine(line)
|
||||
|
||||
|
||||
end )
|
||||
MenuButtonOptions:Open()
|
||||
|
||||
end
|
||||
|
||||
for k, v in pairs(Content.PropsList) do
|
||||
|
||||
PropsList:AddLine(k, v.Class, v.Model)
|
||||
|
||||
end
|
||||
|
||||
local BProps = vgui.Create("DButton", Main)
|
||||
BProps:SetText("Props List")
|
||||
BProps:SetFont("pp_font")
|
||||
BProps:SetSize(160, 50)
|
||||
BProps:SetPos(15, 115 + 85)
|
||||
BProps:SetTextColor( Color( 255, 255, 255, 255 ) )
|
||||
BProps.PaintColor = Color(0, 0, 0, 0)
|
||||
BProps.Paint = function(self)
|
||||
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor)
|
||||
surface.SetDrawColor(17, 148, 240, 255)
|
||||
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
|
||||
end
|
||||
BProps.DoClick = function( self )
|
||||
|
||||
if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end
|
||||
BSelect = self
|
||||
self.PaintColor = Color(17, 148, 240, 100)
|
||||
|
||||
if PSelect then PSelect:Hide() end
|
||||
PropsPanel:Show()
|
||||
PSelect = PropsPanel
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
net.Receive("pp_open_menu", pp_open_menu)
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
____ _ _ ____ __ __ _ _
|
||||
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
|
||||
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
|
||||
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
|
||||
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|
||||
|___/
|
||||
*/
|
||||
|
||||
if not PermaProps then PermaProps = {} end
|
||||
|
||||
function PermaProps.PPGetEntTable( ent )
|
||||
|
||||
if !ent or !ent:IsValid() then return false end
|
||||
|
||||
local content = {}
|
||||
content.Class = ent:GetClass()
|
||||
content.Pos = ent:GetPos()
|
||||
content.Angle = ent:GetAngles()
|
||||
content.Model = ent:GetModel()
|
||||
content.Skin = ent:GetSkin()
|
||||
//content.Mins, content.Maxs = ent:GetCollisionBounds()
|
||||
content.ColGroup = ent:GetCollisionGroup()
|
||||
content.Name = ent:GetName()
|
||||
content.ModelScale = ent:GetModelScale()
|
||||
content.Color = ent:GetColor()
|
||||
content.Material = ent:GetMaterial()
|
||||
content.Solid = ent:GetSolid()
|
||||
content.RenderMode = ent:GetRenderMode()
|
||||
|
||||
if PermaProps.SpecialENTSSave[ent:GetClass()] != nil and isfunction(PermaProps.SpecialENTSSave[ent:GetClass()]) then
|
||||
|
||||
local othercontent = PermaProps.SpecialENTSSave[ent:GetClass()](ent)
|
||||
if not othercontent then return false end
|
||||
if othercontent != nil and istable(othercontent) then
|
||||
table.Merge(content, othercontent)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do
|
||||
local othercontent = hook.Run("PermaProps.OnEntitySaved", ent)
|
||||
if othercontent and istable(othercontent) then
|
||||
table.Merge(content, othercontent)
|
||||
end
|
||||
end
|
||||
|
||||
if ( ent.GetNetworkVars ) then
|
||||
content.DT = ent:GetNetworkVars()
|
||||
end
|
||||
|
||||
local sm = ent:GetMaterials()
|
||||
if ( sm and istable(sm) ) then
|
||||
|
||||
for k, v in pairs( sm ) do
|
||||
|
||||
if ( ent:GetSubMaterial( k )) then
|
||||
|
||||
content.SubMat = content.SubMat or {}
|
||||
content.SubMat[ k ] = ent:GetSubMaterial( k-1 )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local bg = ent:GetBodyGroups()
|
||||
if ( bg ) then
|
||||
|
||||
for k, v in pairs( bg ) do
|
||||
|
||||
if ( ent:GetBodygroup( v.id ) > 0 ) then
|
||||
|
||||
content.BodyG = content.BodyG or {}
|
||||
content.BodyG[ v.id ] = ent:GetBodygroup( v.id )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if ent:GetPhysicsObject() and ent:GetPhysicsObject():IsValid() then
|
||||
content.Frozen = !ent:GetPhysicsObject():IsMoveable()
|
||||
end
|
||||
|
||||
if content.Class == "prop_dynamic" then
|
||||
content.Class = "prop_physics"
|
||||
end
|
||||
|
||||
--content.Table = PermaProps.UselessContent( ent:GetTable() )
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
|
||||
function PermaProps.PPEntityFromTable( data, id )
|
||||
|
||||
if not id or not isnumber(id) then return false end
|
||||
if not data or not istable(data) then return false end
|
||||
|
||||
if data.Class == "prop_physics" and data.Frozen then
|
||||
data.Class = "prop_dynamic" -- Can reduce lags
|
||||
end
|
||||
|
||||
local ent = ents.Create(data.Class)
|
||||
if !ent then return false end
|
||||
if !ent:IsVehicle() then if !ent:IsValid() then return false end end
|
||||
ent:SetPos( data.Pos or Vector(0, 0, 0) )
|
||||
ent:SetAngles( data.Angle or Angle(0, 0, 0) )
|
||||
ent:SetModel( data.Model or "models/error.mdl" )
|
||||
ent:SetSkin( data.Skin or 0 )
|
||||
//ent:SetCollisionBounds( ( data.Mins or 0 ), ( data.Maxs or 0 ) )
|
||||
ent:SetCollisionGroup( data.ColGroup or 0 )
|
||||
ent:SetName( data.Name or "" )
|
||||
ent:SetModelScale( data.ModelScale or 1 )
|
||||
ent:SetMaterial( data.Material or "" )
|
||||
ent:SetSolid( data.Solid or 6 )
|
||||
|
||||
if PermaProps.SpecialENTSSpawn[data.Class] != nil and isfunction(PermaProps.SpecialENTSSpawn[data.Class]) then
|
||||
|
||||
PermaProps.SpecialENTSSpawn[data.Class](ent, data.Other)
|
||||
|
||||
else
|
||||
|
||||
ent:Spawn()
|
||||
|
||||
end
|
||||
|
||||
hook.Run("PermaProps.OnEntityCreated", ent, data)
|
||||
|
||||
ent:SetRenderMode( data.RenderMode or RENDERMODE_NORMAL )
|
||||
ent:SetColor( data.Color or Color(255, 255, 255, 255) )
|
||||
|
||||
if data.EntityMods != nil and istable(data.EntityMods) then -- OLD DATA
|
||||
|
||||
if data.EntityMods.material then
|
||||
ent:SetMaterial( data.EntityMods.material["MaterialOverride"] or "")
|
||||
end
|
||||
|
||||
if data.EntityMods.colour then
|
||||
ent:SetColor( data.EntityMods.colour.Color or Color(255, 255, 255, 255))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if data.DT then
|
||||
|
||||
for k, v in pairs( data.DT ) do
|
||||
|
||||
if ( data.DT[ k ] == nil ) then continue end
|
||||
if !isfunction(ent[ "Set" .. k ]) then continue end
|
||||
ent[ "Set" .. k ]( ent, data.DT[ k ] )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if data.BodyG then
|
||||
|
||||
for k, v in pairs( data.BodyG ) do
|
||||
|
||||
ent:SetBodygroup( k, v )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
if data.SubMat then
|
||||
|
||||
for k, v in pairs( data.SubMat ) do
|
||||
|
||||
if type(k) != "number" or type(v) != "string" then continue end
|
||||
|
||||
ent:SetSubMaterial( k-1, v )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if data.Frozen != nil then
|
||||
|
||||
local phys = ent:GetPhysicsObject()
|
||||
if phys and phys:IsValid() then
|
||||
phys:EnableMotion(!data.Frozen)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
/*if data.Table then
|
||||
|
||||
table.Merge(ent:GetTable(), data.Table)
|
||||
|
||||
end*/
|
||||
|
||||
ent.PermaProps_ID = id
|
||||
ent.PermaProps = true
|
||||
|
||||
// For all idiots who don't know how to config FPP, FUCK YOU
|
||||
function ent:CanTool( ply, trace, tool )
|
||||
|
||||
if trace and IsValid(trace.Entity) and trace.Entity.PermaProps then
|
||||
|
||||
if tool == "permaprops" then
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
return PermaProps.HasPermission( ply, "Tool")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return ent
|
||||
|
||||
end
|
||||
|
||||
function PermaProps.ReloadPermaProps()
|
||||
|
||||
for k, v in pairs( ents.GetAll() ) do
|
||||
|
||||
if v.PermaProps == true then
|
||||
|
||||
v:Remove()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local content = PermaProps.SQL.Query( "SELECT * FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" )
|
||||
|
||||
if not content or content == nil then return end
|
||||
|
||||
for k, v in pairs( content ) do
|
||||
|
||||
local data = util.JSONToTable(v.content)
|
||||
|
||||
local e = PermaProps.PPEntityFromTable(data, tonumber(v.id))
|
||||
if !e or !e:IsValid() then continue end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
hook.Add("InitPostEntity", "InitializePermaProps", PermaProps.ReloadPermaProps)
|
||||
hook.Add("PostCleanupMap", "WhenCleanUpPermaProps", PermaProps.ReloadPermaProps) -- #MOMO
|
||||
timer.Simple(5, function() PermaProps.ReloadPermaProps() end) -- When the hook isn't call ...
|
||||
|
||||
function PermaProps.SparksEffect( ent )
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin(ent:GetPos())
|
||||
effectdata:SetMagnitude(2.5)
|
||||
effectdata:SetScale(2)
|
||||
effectdata:SetRadius(3)
|
||||
util.Effect("Sparks", effectdata, true, true)
|
||||
|
||||
end
|
||||
|
||||
function PermaProps.IsUserGroup( ply, name )
|
||||
|
||||
if not ply:IsValid() then return false end
|
||||
return ply:GetNetworkedString("UserGroup") == name
|
||||
|
||||
end
|
||||
|
||||
function PermaProps.IsAdmin( ply )
|
||||
|
||||
if ( PermaProps.IsUserGroup(ply, "superadmin") or false ) then return true end
|
||||
if ( PermaProps.IsUserGroup(ply, "admin") or false ) then return true end
|
||||
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
function PermaProps.IsSuperAdmin( ply )
|
||||
|
||||
return ( PermaProps.IsUserGroup(ply, "superadmin") or false )
|
||||
|
||||
end
|
||||
|
||||
function PermaProps.UselessContent( tbl )
|
||||
|
||||
local function SortFcn( tbl2 )
|
||||
|
||||
for k, v in pairs( tbl2 ) do
|
||||
|
||||
if isfunction( v ) or isentity( v ) then
|
||||
|
||||
tbl2[k] = nil
|
||||
|
||||
elseif istable( v ) then
|
||||
|
||||
SortFcn( v )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return tbl2
|
||||
|
||||
end
|
||||
|
||||
for k, v in pairs( tbl ) do
|
||||
|
||||
if isfunction( v ) or isentity( v ) then
|
||||
|
||||
tbl[k] = nil
|
||||
|
||||
elseif istable( v ) then
|
||||
|
||||
SortFcn( v )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return tbl
|
||||
|
||||
end
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
____ _ _ ____ __ __ _ _
|
||||
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
|
||||
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
|
||||
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
|
||||
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|
||||
|___/
|
||||
*/
|
||||
|
||||
util.AddNetworkString("pp_open_menu")
|
||||
util.AddNetworkString("pp_info_send")
|
||||
|
||||
local function PermissionLoad()
|
||||
|
||||
if not PermaProps then PermaProps = {} end
|
||||
if not PermaProps.Permissions then PermaProps.Permissions = {} end
|
||||
|
||||
PermaProps.Permissions["superadmin"] = { Physgun = true, Tool = true, Property = true, Save = true, Delete = true, Update = true, Menu = true, Permissions = true, Inherits = "admin", Custom = true }
|
||||
PermaProps.Permissions["admin"] = { Physgun = false, Tool = false, Property = false, Save = true, Delete = true, Update = true, Menu = true, Permissions = false, Inherits = "user", Custom = true }
|
||||
PermaProps.Permissions["user"] = { Physgun = false, Tool = false, Property = false, Save = false, Delete = false, Update = false, Menu = false, Permissions = false, Inherits = "user", Custom = true }
|
||||
|
||||
if CAMI then
|
||||
|
||||
for k, v in pairs(CAMI.GetUsergroups()) do
|
||||
|
||||
if k == "superadmin" or k == "admin" or k == "user" then continue end
|
||||
|
||||
PermaProps.Permissions[k] = { Physgun = false, Tool = false, Property = false, Save = false, Delete = false, Update = false, Menu = false, Permissions = false, Inherits = v.Inherits, Custom = false }
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if file.Exists( "permaprops_config.txt", "DATA" ) then
|
||||
file.Delete( "permaprops_config.txt" )
|
||||
end
|
||||
|
||||
if file.Exists( "permaprops_permissions.txt", "DATA" ) then
|
||||
|
||||
local content = file.Read("permaprops_permissions.txt", "DATA")
|
||||
local tablecontent = util.JSONToTable( content )
|
||||
|
||||
for k, v in pairs(tablecontent) do
|
||||
|
||||
if PermaProps.Permissions[k] == nil then
|
||||
|
||||
tablecontent[k] = nil
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
table.Merge(PermaProps.Permissions, ( tablecontent or {} ))
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
hook.Add("Initialize", "PermaPropPermLoad", PermissionLoad)
|
||||
hook.Add("CAMI.OnUsergroupRegistered", "PermaPropPermLoadCAMI", PermissionLoad) -- In case something changes
|
||||
|
||||
local function PermissionSave()
|
||||
|
||||
file.Write( "permaprops_permissions.txt", util.TableToJSON(PermaProps.Permissions) )
|
||||
|
||||
end
|
||||
|
||||
local function pp_open_menu( ply )
|
||||
|
||||
if !PermaProps.HasPermission( ply, "Menu") then ply:ChatPrint("Access denied !") return end
|
||||
|
||||
local SendTable = {}
|
||||
local Data_PropsList = sql.Query( "SELECT * FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" )
|
||||
|
||||
if Data_PropsList and #Data_PropsList < 200 then
|
||||
|
||||
for k, v in pairs( Data_PropsList ) do
|
||||
|
||||
local data = util.JSONToTable(v.content)
|
||||
|
||||
SendTable[v.id] = {Model = data.Model, Class = data.Class, Pos = data.Pos, Angle = data.Angle}
|
||||
|
||||
end
|
||||
|
||||
elseif Data_PropsList and #Data_PropsList > 200 then -- Too much props dude :'(
|
||||
|
||||
for i = 1, 199 do
|
||||
|
||||
local data = util.JSONToTable(Data_PropsList[i].content)
|
||||
|
||||
SendTable[Data_PropsList[i].id] = {Model = data.Model, Class = data.Class, Pos = data.Pos, Angle = data.Angle}
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local Content = {}
|
||||
Content.MProps = tonumber(sql.QueryValue("SELECT COUNT(*) FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";"))
|
||||
Content.TProps = tonumber(sql.QueryValue("SELECT COUNT(*) FROM permaprops;"))
|
||||
|
||||
Content.PropsList = SendTable
|
||||
Content.Permissions = PermaProps.Permissions
|
||||
|
||||
local Data = util.TableToJSON( Content )
|
||||
local Compressed = util.Compress( Data )
|
||||
|
||||
net.Start( "pp_open_menu" )
|
||||
net.WriteFloat( Compressed:len() )
|
||||
net.WriteData( Compressed, Compressed:len() )
|
||||
net.Send( ply )
|
||||
|
||||
end
|
||||
concommand.Add("pp_cfg_open", pp_open_menu)
|
||||
|
||||
local function pp_info_send( um, ply )
|
||||
|
||||
if !PermaProps.HasPermission( ply, "Menu") then ply:ChatPrint("Access denied !") return end
|
||||
|
||||
local Content = net.ReadTable()
|
||||
|
||||
if Content["CMD"] == "DEL" then
|
||||
|
||||
Content["Val"] = tonumber(Content["Val"])
|
||||
|
||||
if Content["Val"] != nil and Content["Val"] <= 0 then return end
|
||||
|
||||
sql.Query("DELETE FROM permaprops WHERE id = ".. sql.SQLStr(Content["Val"]) .. ";")
|
||||
|
||||
for k, v in pairs(ents.GetAll()) do
|
||||
|
||||
if v.PermaProps_ID == Content["Val"] then
|
||||
|
||||
ply:ChatPrint("You erased " .. v:GetClass() .. " with a model of " .. v:GetModel() .. " from the database.")
|
||||
v:Remove()
|
||||
break
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
elseif Content["CMD"] == "VAR" then
|
||||
|
||||
if PermaProps.Permissions[Content["Name"]] == nil or PermaProps.Permissions[Content["Name"]][Content["Data"]] == nil then return end
|
||||
if !isbool(Content["Val"]) then return end
|
||||
|
||||
if Content["Name"] == "superadmin" and ( Content["Data"] == "Custom" or Content["Data"] == "Permissions" or Content["Data"] == "Menu" ) then return end
|
||||
|
||||
if !PermaProps.HasPermission( ply, "Permissions") then ply:ChatPrint("Access denied !") return end
|
||||
|
||||
PermaProps.Permissions[Content["Name"]][Content["Data"]] = Content["Val"]
|
||||
|
||||
PermissionSave()
|
||||
|
||||
elseif Content["CMD"] == "DEL_MAP" then
|
||||
|
||||
sql.Query( "DELETE FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" )
|
||||
PermaProps.ReloadPermaProps()
|
||||
|
||||
ply:ChatPrint("You erased all props from the map !")
|
||||
|
||||
elseif Content["CMD"] == "DEL_ALL" then
|
||||
|
||||
sql.Query( "DELETE FROM permaprops;" )
|
||||
PermaProps.ReloadPermaProps()
|
||||
|
||||
ply:ChatPrint("You erased all props !")
|
||||
|
||||
elseif Content["CMD"] == "CLR_MAP" then
|
||||
|
||||
for k, v in pairs( ents.GetAll() ) do
|
||||
|
||||
if v.PermaProps == true then
|
||||
|
||||
v:Remove()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
ply:ChatPrint("You have removed all props !")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
net.Receive("pp_info_send", pp_info_send)
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
____ _ _ ____ __ __ _ _
|
||||
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
|
||||
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
|
||||
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
|
||||
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|
||||
|___/
|
||||
Thanks to ARitz Cracker for this part
|
||||
*/
|
||||
|
||||
function PermaProps.HasPermission( ply, name )
|
||||
|
||||
if !PermaProps or !PermaProps.Permissions or !PermaProps.Permissions[ply:GetUserGroup()] then return false end
|
||||
|
||||
if PermaProps.Permissions[ply:GetUserGroup()].Custom == false and PermaProps.Permissions[ply:GetUserGroup()].Inherits and PermaProps.Permissions[PermaProps.Permissions[ply:GetUserGroup()].Inherits] then
|
||||
|
||||
return PermaProps.Permissions[PermaProps.Permissions[ply:GetUserGroup()].Inherits][name]
|
||||
|
||||
end
|
||||
|
||||
return PermaProps.Permissions[ply:GetUserGroup()][name]
|
||||
|
||||
end
|
||||
|
||||
local function PermaPropsPhys( ply, ent, phys )
|
||||
|
||||
if ent.PermaProps then
|
||||
|
||||
return PermaProps.HasPermission( ply, "Physgun")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
hook.Add("PhysgunPickup", "PermaPropsPhys", PermaPropsPhys)
|
||||
hook.Add( "CanPlayerUnfreeze", "PermaPropsUnfreeze", PermaPropsPhys) -- Prevents people from pressing RELOAD on the physgun
|
||||
|
||||
local function PermaPropsTool( ply, tr, tool )
|
||||
|
||||
if IsValid(tr.Entity) then
|
||||
|
||||
if tr.Entity.PermaProps then
|
||||
|
||||
if tool == "permaprops" then
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
return PermaProps.HasPermission( ply, "Tool")
|
||||
|
||||
end
|
||||
|
||||
if tr.Entity:GetClass() == "sammyservers_textscreen" and tool == "permaprops" then -- Let people use PermaProps on textscreen
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
hook.Add( "CanTool", "PermaPropsTool", PermaPropsTool)
|
||||
|
||||
local function PermaPropsProperty( ply, property, ent )
|
||||
|
||||
if IsValid(ent) and ent.PermaProps and tool ~= "permaprops" then
|
||||
|
||||
return PermaProps.HasPermission( ply, "Property")
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
hook.Add( "CanProperty", "PermaPropsProperty", PermaPropsProperty)
|
||||
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
____ _ _ ____ __ __ _ _
|
||||
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
|
||||
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
|
||||
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
|
||||
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|
||||
|___/
|
||||
*/
|
||||
|
||||
if not PermaProps then PermaProps = {} end
|
||||
|
||||
PermaProps.SpecialENTSSpawn = {}
|
||||
PermaProps.SpecialENTSSpawn["gmod_lamp"] = function( ent, data)
|
||||
|
||||
ent:SetFlashlightTexture( data["Texture"] )
|
||||
ent:SetLightFOV( data["fov"] )
|
||||
ent:SetColor( Color( data["r"], data["g"], data["b"], 255 ) )
|
||||
ent:SetDistance( data["distance"] )
|
||||
ent:SetBrightness( data["brightness"] )
|
||||
ent:Switch( true )
|
||||
|
||||
ent:Spawn()
|
||||
|
||||
ent.Texture = data["Texture"]
|
||||
ent.KeyDown = data["KeyDown"]
|
||||
ent.fov = data["fov"]
|
||||
ent.distance = data["distance"]
|
||||
ent.r = data["r"]
|
||||
ent.g = data["g"]
|
||||
ent.b = data["b"]
|
||||
ent.brightness = data["brightness"]
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"] = function( ent, data)
|
||||
|
||||
if ( ent:GetModel() == "models/buggy.mdl" ) then ent:SetKeyValue( "vehiclescript", "scripts/vehicles/jeep_test.txt" ) end
|
||||
if ( ent:GetModel() == "models/vehicle.mdl" ) then ent:SetKeyValue( "vehiclescript", "scripts/vehicles/jalopy.txt" ) end
|
||||
|
||||
if ( data["VehicleTable"] && data["VehicleTable"].KeyValues ) then
|
||||
for k, v in pairs( data["VehicleTable"].KeyValues ) do
|
||||
ent:SetKeyValue( k, v )
|
||||
end
|
||||
end
|
||||
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
ent:SetVehicleClass( data["VehicleName"] )
|
||||
ent.VehicleName = data["VehicleName"]
|
||||
ent.VehicleTable = data["VehicleTable"]
|
||||
ent.ClassOverride = data["Class"]
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
PermaProps.SpecialENTSSpawn["prop_vehicle_jeep_old"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"]
|
||||
PermaProps.SpecialENTSSpawn["prop_vehicle_airboat"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"]
|
||||
PermaProps.SpecialENTSSpawn["prop_vehicle_prisoner_pod"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"]
|
||||
|
||||
PermaProps.SpecialENTSSpawn["prop_ragdoll"] = function( ent, data )
|
||||
|
||||
if !data or !istable( data ) then return end
|
||||
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
if data["Bones"] then
|
||||
|
||||
for objectid, objectdata in pairs( data["Bones"] ) do
|
||||
|
||||
local Phys = ent:GetPhysicsObjectNum( objectid )
|
||||
if !IsValid( Phys ) then continue end
|
||||
|
||||
if ( isvector( objectdata.Pos ) && isangle( objectdata.Angle ) ) then
|
||||
|
||||
local pos, ang = LocalToWorld( objectdata.Pos, objectdata.Angle, Vector(0, 0, 0), Angle(0, 0, 0) )
|
||||
Phys:SetPos( pos )
|
||||
Phys:SetAngles( ang )
|
||||
Phys:Wake()
|
||||
|
||||
if objectdata.Frozen then
|
||||
Phys:EnableMotion( false )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if data["BoneManip"] and ent:IsValid() then
|
||||
|
||||
for k, v in pairs( data["BoneManip"] ) do
|
||||
|
||||
if ( v.s ) then ent:ManipulateBoneScale( k, v.s ) end
|
||||
if ( v.a ) then ent:ManipulateBoneAngles( k, v.a ) end
|
||||
if ( v.p ) then ent:ManipulateBonePosition( k, v.p ) end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if data["Flex"] and ent:IsValid() then
|
||||
|
||||
for k, v in pairs( data["Flex"] ) do
|
||||
ent:SetFlexWeight( k, v )
|
||||
end
|
||||
|
||||
if ( Scale ) then
|
||||
ent:SetFlexScale( Scale )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
PermaProps.SpecialENTSSpawn["sammyservers_textscreen"] = function( ent, data )
|
||||
|
||||
if !data or !istable( data ) then return end
|
||||
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
if data["Lines"] then
|
||||
|
||||
for k, v in pairs(data["Lines"] or {}) do
|
||||
|
||||
ent:SetLine(k, v.text, Color(v.color.r, v.color.g, v.color.b, v.color.a), v.size, v.font, v.rainbow or 0)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
PermaProps.SpecialENTSSpawn["NPC"] = function( ent, data )
|
||||
|
||||
if data and istable( data ) then
|
||||
|
||||
if data["Equipment"] then
|
||||
|
||||
local valid = false
|
||||
for _, v in pairs( list.Get( "NPCUsableWeapons" ) ) do
|
||||
if v.class == data["Equipment"] then valid = true break end
|
||||
end
|
||||
|
||||
if ( data["Equipment"] && data["Equipment"] != "none" && valid ) then
|
||||
ent:SetKeyValue( "additionalequipment", data["Equipment"] )
|
||||
ent.Equipment = data["Equipment"]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
if list.Get( "NPC" ) and istable(list.Get( "NPC" )) then
|
||||
|
||||
for k, v in pairs(list.Get( "NPC" )) do
|
||||
|
||||
PermaProps.SpecialENTSSpawn[k] = PermaProps.SpecialENTSSpawn["NPC"]
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
PermaProps.SpecialENTSSpawn["item_ammo_crate"] = function( ent, data )
|
||||
|
||||
if data and istable(data) and data["type"] then
|
||||
|
||||
ent.type = data["type"]
|
||||
ent:SetKeyValue( "AmmoType", math.Clamp( data["type"], 0, 9 ) )
|
||||
|
||||
end
|
||||
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
|
||||
PermaProps.SpecialENTSSave = {}
|
||||
PermaProps.SpecialENTSSave["gmod_lamp"] = function( ent )
|
||||
|
||||
local content = {}
|
||||
content.Other = {}
|
||||
content.Other["Texture"] = ent.Texture
|
||||
content.Other["KeyDown"] = ent.KeyDown
|
||||
content.Other["fov"] = ent.fov
|
||||
content.Other["distance"] = ent.distance
|
||||
content.Other["r"] = ent.r
|
||||
content.Other["g"] = ent.g
|
||||
content.Other["b"] = ent.b
|
||||
content.Other["brightness"] = ent.brightness
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
|
||||
PermaProps.SpecialENTSSave["prop_vehicle_jeep"] = function( ent )
|
||||
|
||||
if not ent.VehicleTable then return false end
|
||||
|
||||
local content = {}
|
||||
content.Other = {}
|
||||
content.Other["VehicleName"] = ent.VehicleName
|
||||
content.Other["VehicleTable"] = ent.VehicleTable
|
||||
content.Other["ClassOverride"] = ent.ClassOverride
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
PermaProps.SpecialENTSSave["prop_vehicle_jeep_old"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"]
|
||||
PermaProps.SpecialENTSSave["prop_vehicle_airboat"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"]
|
||||
PermaProps.SpecialENTSSave["prop_vehicle_prisoner_pod"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"]
|
||||
|
||||
PermaProps.SpecialENTSSave["prop_ragdoll"] = function( ent )
|
||||
|
||||
local content = {}
|
||||
content.Other = {}
|
||||
content.Other["Bones"] = {}
|
||||
|
||||
local num = ent:GetPhysicsObjectCount()
|
||||
for objectid = 0, num - 1 do
|
||||
|
||||
local obj = ent:GetPhysicsObjectNum( objectid )
|
||||
if ( !obj:IsValid() ) then continue end
|
||||
|
||||
content.Other["Bones"][ objectid ] = {}
|
||||
|
||||
content.Other["Bones"][ objectid ].Pos = obj:GetPos()
|
||||
content.Other["Bones"][ objectid ].Angle = obj:GetAngles()
|
||||
content.Other["Bones"][ objectid ].Frozen = !obj:IsMoveable()
|
||||
if ( obj:IsAsleep() ) then content.Other["Bones"][ objectid ].Sleep = true end
|
||||
|
||||
content.Other["Bones"][ objectid ].Pos, content.Other["Bones"][ objectid ].Angle = WorldToLocal( content.Other["Bones"][ objectid ].Pos, content.Other["Bones"][ objectid ].Angle, Vector( 0, 0, 0 ), Angle( 0, 0, 0 ) )
|
||||
|
||||
end
|
||||
|
||||
if ( ent:HasBoneManipulations() ) then
|
||||
|
||||
content.Other["BoneManip"] = {}
|
||||
|
||||
for i = 0, ent:GetBoneCount() do
|
||||
|
||||
local t = {}
|
||||
|
||||
local s = ent:GetManipulateBoneScale( i )
|
||||
local a = ent:GetManipulateBoneAngles( i )
|
||||
local p = ent:GetManipulateBonePosition( i )
|
||||
|
||||
if ( s != Vector( 1, 1, 1 ) ) then t[ 's' ] = s end -- scale
|
||||
if ( a != Angle( 0, 0, 0 ) ) then t[ 'a' ] = a end -- angle
|
||||
if ( p != Vector( 0, 0, 0 ) ) then t[ 'p' ] = p end -- position
|
||||
|
||||
if ( table.Count( t ) > 0 ) then
|
||||
content.Other["BoneManip"][ i ] = t
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
content.Other["FlexScale"] = ent:GetFlexScale()
|
||||
for i = 0, ent:GetFlexNum() do
|
||||
|
||||
local w = ent:GetFlexWeight( i )
|
||||
if ( w != 0 ) then
|
||||
content.Other["Flex"] = content.Other["Flex"] or {}
|
||||
content.Other["Flex"][ i ] = w
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
|
||||
PermaProps.SpecialENTSSave["sammyservers_textscreen"] = function( ent )
|
||||
|
||||
local content = {}
|
||||
content.Other = {}
|
||||
content.Other["Lines"] = ent.lines or {}
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
|
||||
PermaProps.SpecialENTSSave["prop_effect"] = function( ent )
|
||||
|
||||
local content = {}
|
||||
content.Class = "pp_prop_effect"
|
||||
content.Model = ent.AttachedEntity:GetModel()
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
PermaProps.SpecialENTSSave["pp_prop_effect"] = PermaProps.SpecialENTSSave["prop_effect"]
|
||||
|
||||
PermaProps.SpecialENTSSave["NPC"] = function( ent )
|
||||
|
||||
if !ent.Equipment then return {} end
|
||||
|
||||
local content = {}
|
||||
content.Other = {}
|
||||
content.Other["Equipment"] = ent.Equipment
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
|
||||
if list.Get( "NPC" ) and istable(list.Get( "NPC" )) then
|
||||
|
||||
for k, v in pairs(list.Get( "NPC" )) do
|
||||
|
||||
PermaProps.SpecialENTSSave[k] = PermaProps.SpecialENTSSave["NPC"]
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
PermaProps.SpecialENTSSave["item_ammo_crate"] = function( ent )
|
||||
|
||||
local content = {}
|
||||
content.Other = {}
|
||||
content.Other["type"] = ent.type
|
||||
|
||||
return content
|
||||
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
____ _ _ ____ __ __ _ _
|
||||
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
|
||||
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
|
||||
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
|
||||
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|
||||
|___/
|
||||
*/
|
||||
|
||||
sql.Query("CREATE TABLE IF NOT EXISTS permaprops('id' INTEGER NOT NULL, 'map' TEXT NOT NULL, 'content' TEXT NOT NULL, PRIMARY KEY('id'));")
|
||||
|
||||
if not PermaProps then PermaProps = {} end
|
||||
|
||||
PermaProps.SQL = {}
|
||||
|
||||
/* NOT WORKS AT THE MOMENT
|
||||
PermaProps.SQL.MySQL = false
|
||||
PermaProps.SQL.Host = "127.0.0.1"
|
||||
PermaProps.SQL.Username = "username"
|
||||
PermaProps.SQL.Password = "password"
|
||||
PermaProps.SQL.Database_name = "PermaProps"
|
||||
PermaProps.SQL.Database_port = 3306
|
||||
PermaProps.SQL.Preferred_module = "mysqloo"
|
||||
*/
|
||||
|
||||
function PermaProps.SQL.Query( data )
|
||||
|
||||
return sql.Query( data )
|
||||
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
PLUGIN.name = "Build Tools"
|
||||
PLUGIN.author = "Various (Портирован для Helix)"
|
||||
PLUGIN.description = "Набор строительных инструментов: AdvDupe2, Permaprops, Precision Tool"
|
||||
|
||||
-- Устанавливаем высокий приоритет загрузки, чтобы плагин загрузился раньше инструментов
|
||||
PLUGIN.priority = 100
|
||||
|
||||
-- Конфигурация
|
||||
ix.config.Add("buildToolsEnabled", true, "Включить строительные инструменты", nil, {
|
||||
category = "Build Tools"
|
||||
})
|
||||
|
||||
ix.config.Add("buildToolsAdvDupe2", true, "Включить AdvDupe2 (дублирование)", nil, {
|
||||
category = "Build Tools"
|
||||
})
|
||||
|
||||
ix.config.Add("buildToolsPermaprops", true, "Включить Permaprops (постоянные пропы)", nil, {
|
||||
category = "Build Tools"
|
||||
})
|
||||
|
||||
ix.config.Add("buildToolsPrecision", true, "Включить Precision Tool (точное позиционирование)", nil, {
|
||||
category = "Build Tools"
|
||||
})
|
||||
|
||||
ix.config.Add("buildToolsNoCollideWorld", true, "Включить NoCollide World (отключение коллизий с миром)", nil, {
|
||||
category = "Build Tools"
|
||||
})
|
||||
|
||||
-- Загрузка NetStream (библиотека для сетевых сообщений)
|
||||
ix.util.Include("netstream.lua")
|
||||
|
||||
-- Загрузка AdvDupe2
|
||||
if ix.config.Get("buildToolsAdvDupe2", true) then
|
||||
-- Инициализация глобальной таблицы AdvDupe2
|
||||
AdvDupe2 = AdvDupe2 or {}
|
||||
print("[Build Tools] Initializing AdvDupe2...")
|
||||
|
||||
-- Shared
|
||||
ix.util.Include("advdupe2/sh_codec_legacy.lua")
|
||||
ix.util.Include("advdupe2/sh_codec.lua")
|
||||
|
||||
-- Server
|
||||
ix.util.Include("advdupe2/sv_file.lua")
|
||||
ix.util.Include("advdupe2/sv_ghost.lua")
|
||||
ix.util.Include("advdupe2/sv_clipboard.lua")
|
||||
ix.util.Include("advdupe2/sv_misc.lua")
|
||||
|
||||
-- Client
|
||||
ix.util.Include("advdupe2/cl_file.lua")
|
||||
ix.util.Include("advdupe2/cl_ghost.lua")
|
||||
ix.util.Include("advdupe2/file_browser.lua")
|
||||
|
||||
print("[Build Tools] AdvDupe2 loaded successfully")
|
||||
end
|
||||
|
||||
-- Загрузка Permaprops
|
||||
if ix.config.Get("buildToolsPermaprops", true) then
|
||||
-- Server
|
||||
ix.util.Include("permaprops/sv_lib.lua")
|
||||
ix.util.Include("permaprops/sv_menu.lua")
|
||||
ix.util.Include("permaprops/sv_permissions.lua")
|
||||
ix.util.Include("permaprops/sv_specialfcn.lua")
|
||||
ix.util.Include("permaprops/sv_sql.lua")
|
||||
|
||||
-- Client
|
||||
ix.util.Include("permaprops/cl_drawent.lua")
|
||||
ix.util.Include("permaprops/cl_menu.lua")
|
||||
end
|
||||
|
||||
function PLUGIN:Initialize()
|
||||
print("[Build Tools] Загружены строительные инструменты!")
|
||||
|
||||
if ix.config.Get("buildToolsAdvDupe2", true) then
|
||||
print("[Build Tools] AdvDupe2 активирован")
|
||||
end
|
||||
|
||||
if ix.config.Get("buildToolsPermaprops", true) then
|
||||
print("[Build Tools] Permaprops активирован")
|
||||
end
|
||||
end
|
||||
135
garrysmod/gamemodes/militaryrp/plugins/capture/cl_plugin.lua
Normal file
135
garrysmod/gamemodes/militaryrp/plugins/capture/cl_plugin.lua
Normal file
@@ -0,0 +1,135 @@
|
||||
PLUGIN.captureData = PLUGIN.captureData or {}
|
||||
|
||||
net.Receive("ixCaptureSync", function()
|
||||
local plugin = ix.plugin.Get("capture")
|
||||
if (!plugin) then return end
|
||||
|
||||
local point = net.ReadEntity()
|
||||
local progress = net.ReadFloat()
|
||||
local owner = net.ReadUInt(8)
|
||||
local capturing = net.ReadUInt(8)
|
||||
local players = net.ReadUInt(8)
|
||||
local blocked = net.ReadBool()
|
||||
|
||||
if (IsValid(point)) then
|
||||
plugin.captureData[point] = {
|
||||
progress = progress,
|
||||
owner = owner,
|
||||
capturing = capturing,
|
||||
players = players,
|
||||
blocked = blocked
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.progress = 0
|
||||
self.targetProgress = 0
|
||||
self.alpha = 0
|
||||
self.targetAlpha = 0
|
||||
self.blinkAlpha = 0
|
||||
end
|
||||
|
||||
function PANEL:SetCaptureData(data)
|
||||
self.targetProgress = data.progress or 0
|
||||
self.owner = data.owner or 0
|
||||
self.capturing = data.capturing or 0
|
||||
self.players = data.players or 0
|
||||
self.blocked = data.blocked or false
|
||||
|
||||
if (data.capturing and data.capturing > 0) then
|
||||
self.targetAlpha = 255
|
||||
else
|
||||
self.targetAlpha = 0
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
self.progress = Lerp(FrameTime() * 5, self.progress, self.targetProgress)
|
||||
self.alpha = Lerp(FrameTime() * 8, self.alpha, self.targetAlpha)
|
||||
|
||||
if (self.blocked) then
|
||||
self.blinkAlpha = math.abs(math.sin(CurTime() * 5)) * 255
|
||||
else
|
||||
self.blinkAlpha = 0
|
||||
end
|
||||
|
||||
local plugin = ix.plugin.Get("capture")
|
||||
if (plugin) then
|
||||
self:SetSize(plugin.hudWidth, plugin.hudHeight)
|
||||
self:SetPos(ScrW() - plugin.hudWidth - plugin.hudPosX, plugin.hudPosY)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint(w, h)
|
||||
if (self.alpha < 1) then return end
|
||||
|
||||
surface.SetAlphaMultiplier(self.alpha / 255)
|
||||
|
||||
local barW = 40
|
||||
local barH = h
|
||||
local padding = 10
|
||||
local barX = (w - barW) / 2
|
||||
|
||||
draw.RoundedBox(8, barX, 0, barW, barH, ColorAlpha(Color(20, 20, 20), self.alpha * 0.9))
|
||||
|
||||
if (self.progress > 0) then
|
||||
local fillH = (barH - 4) * (self.progress / 100)
|
||||
local color = Color(100, 150, 255)
|
||||
|
||||
if (self.capturing and self.capturing > 0) then
|
||||
if (self.capturing == FACTION_RUSSIAN) then
|
||||
color = Color(200, 50, 50)
|
||||
elseif (self.capturing == FACTION_UKRAINE) then
|
||||
color = Color(50, 100, 200)
|
||||
end
|
||||
end
|
||||
|
||||
if (self.blocked) then
|
||||
color = ColorAlpha(color, self.blinkAlpha)
|
||||
end
|
||||
|
||||
draw.RoundedBox(6, barX + 2, 2, barW - 4, fillH, ColorAlpha(color, self.alpha))
|
||||
end
|
||||
|
||||
local text = string.format("%.0f%%", self.progress)
|
||||
if (self.players and self.players > 0) then
|
||||
text = text .. "\n(" .. self.players .. ")"
|
||||
end
|
||||
|
||||
draw.SimpleText(text, "DermaDefault", w / 2, barH + 15, ColorAlpha(color_white, self.alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
|
||||
|
||||
surface.SetAlphaMultiplier(1)
|
||||
end
|
||||
|
||||
vgui.Register("ixCaptureUI", PANEL, "DPanel")
|
||||
|
||||
function PLUGIN:HUDPaint()
|
||||
local ply = LocalPlayer()
|
||||
if (!IsValid(ply)) then return end
|
||||
|
||||
for point, data in pairs(self.captureData) do
|
||||
if (IsValid(point) and ply:GetPos():Distance(point:GetPos()) <= self.captureRadius) then
|
||||
if (!IsValid(self.captureUI)) then
|
||||
self.captureUI = vgui.Create("ixCaptureUI")
|
||||
end
|
||||
|
||||
self.captureUI:SetCaptureData(data)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if (IsValid(self.captureUI)) then
|
||||
self.captureUI:SetCaptureData({progress = 0, owner = 0, capturing = 0, players = 0, blocked = false})
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:OnReloaded()
|
||||
if (IsValid(self.captureUI)) then
|
||||
self.captureUI:Remove()
|
||||
self.captureUI = nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
ENT.Type = "anim"
|
||||
ENT.PrintName = "Точка захвата"
|
||||
ENT.Category = "[FT] Захват точек"
|
||||
ENT.Spawnable = true
|
||||
ENT.AdminOnly = true
|
||||
ENT.RenderGroup = RENDERGROUP_BOTH
|
||||
ENT.bNoPersist = true
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:NetworkVar("Float", 0, "CaptureProgress")
|
||||
self:NetworkVar("Int", 0, "OwnerFaction")
|
||||
self:NetworkVar("Int", 1, "FlagSkin")
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
function ENT:Initialize()
|
||||
self:SetModel("models/flag/ua_flag_rework.mdl")
|
||||
self:PhysicsInit(SOLID_VPHYSICS)
|
||||
self:SetMoveType(MOVETYPE_NONE)
|
||||
self:SetSolid(SOLID_VPHYSICS)
|
||||
self:SetUseType(SIMPLE_USE)
|
||||
|
||||
local phys = self:GetPhysicsObject()
|
||||
if (IsValid(phys)) then
|
||||
phys:EnableMotion(false)
|
||||
end
|
||||
|
||||
-- Установка начального скина (никто не захватил)
|
||||
self:SetFlagSkin(2)
|
||||
self:SetSkin(2)
|
||||
self:SetOwnerFaction(0)
|
||||
|
||||
local plugin = ix.plugin.Get("capture")
|
||||
if (plugin) then
|
||||
plugin:RegisterCapturePoint(self)
|
||||
end
|
||||
end
|
||||
|
||||
-- Функция для установки владельца с автоматической сменой скина
|
||||
function ENT:SetOwnerFactionID(factionID)
|
||||
self:SetOwnerFaction(factionID or 0)
|
||||
|
||||
local skinID = 2 -- По умолчанию никто
|
||||
|
||||
if factionID == FACTION_RUSSIAN then
|
||||
skinID = 1 -- РФ - скин 1
|
||||
elseif factionID == FACTION_UKRAINE then
|
||||
skinID = 0 -- ВСУ - скин 0
|
||||
end
|
||||
|
||||
self:SetFlagSkin(skinID)
|
||||
self:SetSkin(skinID)
|
||||
end
|
||||
|
||||
function ENT:Use(activator, caller)
|
||||
end
|
||||
else
|
||||
function ENT:Draw()
|
||||
self:DrawModel()
|
||||
|
||||
-- Синхронизация скина с NetworkVar
|
||||
local skinID = self:GetFlagSkin()
|
||||
if skinID and self:GetSkin() ~= skinID then
|
||||
self:SetSkin(skinID)
|
||||
end
|
||||
|
||||
--local plugin = ix.plugin.Get("capture")
|
||||
--if (!plugin or !plugin.captureData[self]) then return end
|
||||
--
|
||||
--local data = plugin.captureData[self]
|
||||
--local pos = self:GetPos() + Vector(0, 0, plugin.labelOffsetZ)
|
||||
--local ang = EyeAngles()
|
||||
--ang:RotateAroundAxis(ang:Forward(), 90)
|
||||
--ang:RotateAroundAxis(ang:Right(), 90)
|
||||
|
||||
--cam.Start3D2D(pos, Angle(0, ang.y, 90), plugin.labelScale)
|
||||
-- if (data.capturing and data.capturing > 0) then
|
||||
-- local barW = 400
|
||||
-- local barH = 60
|
||||
-- local x, y = -barW / 2, -barH / 2
|
||||
--
|
||||
-- draw.RoundedBox(8, x, y - 80, barW, barH, Color(20, 20, 20, 230))
|
||||
--
|
||||
-- local fillW = (barW - 8) * (data.progress / 100)
|
||||
-- local color = Color(100, 150, 255)
|
||||
--
|
||||
-- if (data.capturing == FACTION_RUSSIAN) then
|
||||
-- color = Color(200, 50, 50)
|
||||
-- elseif (data.capturing == FACTION_UKRAINE) then
|
||||
-- color = Color(50, 100, 200)
|
||||
-- end
|
||||
--
|
||||
-- draw.RoundedBox(6, x + 4, y - 76, fillW, barH - 8, color)
|
||||
--
|
||||
-- local text = string.format("%.0f%%", data.progress)
|
||||
-- draw.SimpleText(text, "DermaLarge", 0, -80, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
-- end
|
||||
--
|
||||
-- local ownerText = "Никем не захвачена"
|
||||
-- local ownerColor = Color(150, 150, 150)
|
||||
--
|
||||
-- if (data.owner and data.owner > 0) then
|
||||
-- local faction = ix.faction.Get(data.owner)
|
||||
-- if (faction) then
|
||||
-- ownerText = faction.name
|
||||
-- if (data.owner == FACTION_RUSSIAN) then
|
||||
-- ownerColor = Color(200, 50, 50)
|
||||
-- elseif (data.owner == FACTION_UKRAINE) then
|
||||
-- ownerColor = Color(50, 100, 200)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- draw.SimpleText(ownerText, "DermaLarge", 0, 0, ownerColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
--cam.End3D2D()
|
||||
end
|
||||
end
|
||||
44
garrysmod/gamemodes/militaryrp/plugins/capture/sh_plugin.lua
Normal file
44
garrysmod/gamemodes/militaryrp/plugins/capture/sh_plugin.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
PLUGIN.name = "Capture Points"
|
||||
PLUGIN.author = "RefoselTeamWork"
|
||||
PLUGIN.description = "Система захвата точек"
|
||||
|
||||
PLUGIN.captureRadius = 1024
|
||||
PLUGIN.captureBaseTime = 35
|
||||
PLUGIN.moneyReward = 3000
|
||||
PLUGIN.arsenalReward = 500
|
||||
PLUGIN.vehicleReward = 500
|
||||
|
||||
-- Настройки периодических наград
|
||||
PLUGIN.tickInterval = 60 -- Интервал начисления в секундах
|
||||
PLUGIN.tickSupplyPerPoint = 200 -- Очки снабжения за одну точку
|
||||
PLUGIN.tickVehiclePerPoint = 150 -- Очки техники за одну точку
|
||||
|
||||
PLUGIN.hudPosX = 0
|
||||
PLUGIN.hudPosY = 20
|
||||
PLUGIN.hudWidth = 200
|
||||
PLUGIN.hudHeight = 300
|
||||
|
||||
PLUGIN.labelOffsetZ = 80
|
||||
PLUGIN.labelScale = 0.1
|
||||
|
||||
PLUGIN.factionMinOnline = {
|
||||
[FACTION_RUSSIAN] = 1,
|
||||
[FACTION_UKRAINE] = 0
|
||||
}
|
||||
|
||||
PLUGIN.defaultPoints = {
|
||||
{pos = Vector(-6788.718262, -2758.777832, 620.980103), ang = Angle(0, 90, 0)},
|
||||
{pos = Vector(-386.355896, -13121.211914, 139.868973), ang = Angle(0, 90, 0)},
|
||||
|
||||
{pos = Vector(4969.474121, -10055.811523, 114.868973), ang = Angle(0, 90, 0)},
|
||||
{pos = Vector(15625.968750, -13238.951172, 282.868958), ang = Angle(0, 90, 0)},
|
||||
{pos = Vector(12809.052734, -4790.340820, 115.868973), ang = Angle(0, 90, 0)},
|
||||
{pos = Vector(13539.985352, 4858.571289, 121.868973), ang = Angle(0, 90, 0)},
|
||||
{pos = Vector(1916.585205, 2236.532715, 226.868958), ang = Angle(0, 90, 0)},
|
||||
{pos = Vector(5527.783203, 2510.990479, 736.868958), ang = Angle(0, 90, 0)},
|
||||
}
|
||||
|
||||
PLUGIN.minCapturePlayers = 1
|
||||
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
414
garrysmod/gamemodes/militaryrp/plugins/capture/sv_plugin.lua
Normal file
414
garrysmod/gamemodes/militaryrp/plugins/capture/sv_plugin.lua
Normal file
@@ -0,0 +1,414 @@
|
||||
util.AddNetworkString("ixCaptureSync")
|
||||
|
||||
PLUGIN.capturePoints = PLUGIN.capturePoints or {}
|
||||
|
||||
function PLUGIN:GetPlayersInRadius(point, radius)
|
||||
local players = {}
|
||||
local pos = point:GetPos()
|
||||
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if ply:Alive() and pos:Distance(ply:GetPos()) <= radius and not (ply.IsAdminMode and ply:IsAdminMode()) then
|
||||
table.insert(players, ply)
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
function PLUGIN:GetFactionFromPlayers(players)
|
||||
local factions = {}
|
||||
|
||||
for _, ply in ipairs(players) do
|
||||
local char = ply:GetCharacter()
|
||||
if (char) then
|
||||
local faction = char:GetFaction()
|
||||
factions[faction] = (factions[faction] or 0) + 1
|
||||
end
|
||||
end
|
||||
|
||||
return factions
|
||||
end
|
||||
|
||||
function PLUGIN:GetFactionOnlineCount(faction)
|
||||
local count = 0
|
||||
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
local char = ply:GetCharacter()
|
||||
if char and char:GetFaction() == faction and not (ply.IsAdminMode and ply:IsAdminMode()) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
function PLUGIN:CanFactionCapture(faction)
|
||||
local minOnline = self.factionMinOnline[faction]
|
||||
if (!minOnline) then return true end
|
||||
|
||||
local online = self:GetFactionOnlineCount(faction)
|
||||
return online >= minOnline
|
||||
end
|
||||
|
||||
function PLUGIN:CanCapture(factions)
|
||||
local count = 0
|
||||
for _ in pairs(factions) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return count == 1
|
||||
end
|
||||
|
||||
function PLUGIN:GiveRewards(faction, players)
|
||||
for _, ply in ipairs(players) do
|
||||
local char = ply:GetCharacter()
|
||||
if (char) then
|
||||
char:GiveMoney(self.moneyReward)
|
||||
ply:Notify("Вы получили " .. self.moneyReward .. " за захват точки!")
|
||||
end
|
||||
end
|
||||
|
||||
if (ix.plugin.list["arsenal"]) then
|
||||
ix.plugin.list["arsenal"]:AddFactionSupply(faction, self.arsenalReward)
|
||||
end
|
||||
|
||||
if (ix.plugin.list["vehicles"]) then
|
||||
ix.plugin.list["vehicles"]:AddFactionPoints(faction, self.vehicleReward)
|
||||
end
|
||||
|
||||
-- Логируем выдачу наград
|
||||
local serverlogsPlugin = ix.plugin.list["serverlogs"]
|
||||
if (serverlogsPlugin) then
|
||||
local factionName = ix.faction.Get(faction).name or tostring(faction)
|
||||
local message = string.format("Фракция '%s' получила награду за захват: %d снабжения, %d очков техники, %d денег на игрока",
|
||||
factionName, self.arsenalReward, self.vehicleReward, self.moneyReward)
|
||||
serverlogsPlugin:AddLog("CAPTURE_REWARD", message, nil, {
|
||||
faction = faction,
|
||||
factionName = factionName,
|
||||
arsenalReward = self.arsenalReward,
|
||||
vehicleReward = self.vehicleReward,
|
||||
moneyReward = self.moneyReward,
|
||||
playerCount = #players
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:ProcessCapture(point)
|
||||
local players = self:GetPlayersInRadius(point, self.captureRadius)
|
||||
|
||||
if (#players == 0) then
|
||||
if point.capturingFaction != nil then
|
||||
point.capturingFaction = nil
|
||||
point.capturingPlayers = 0
|
||||
self:SyncCapturePoint(point)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local factions = self:GetFactionFromPlayers(players)
|
||||
|
||||
if (!self:CanCapture(factions)) then
|
||||
if point.capturingFaction != nil or !point.captureBlocked then
|
||||
point.capturingFaction = nil
|
||||
point.capturingPlayers = 0
|
||||
point.captureBlocked = true
|
||||
self:SyncCapturePoint(point)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
point.captureBlocked = false
|
||||
|
||||
local faction, playerCount = next(factions)
|
||||
|
||||
if (playerCount < self.minCapturePlayers) then
|
||||
if point.capturingFaction != nil or !point.captureBlocked then
|
||||
point.capturingFaction = nil
|
||||
point.capturingPlayers = 0
|
||||
point.captureBlocked = true
|
||||
self:SyncCapturePoint(point)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if (!self:CanFactionCapture(faction)) then
|
||||
if point.capturingFaction != nil or !point.captureBlocked then
|
||||
point.capturingFaction = nil
|
||||
point.capturingPlayers = 0
|
||||
point.captureBlocked = true
|
||||
self:SyncCapturePoint(point)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if (point.ownerFaction == faction) then
|
||||
if point.captureProgress != 100 or point.capturingFaction != nil then
|
||||
point.captureProgress = 100
|
||||
point.capturingFaction = nil
|
||||
point.capturingPlayers = 0
|
||||
self:SyncCapturePoint(point)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Логируем начало захвата при первом касании
|
||||
if (!point.capturingFaction or point.capturingFaction != faction) then
|
||||
-- Сбрасываем прогресс если начинается захват другой фракцией
|
||||
point.captureProgress = 0
|
||||
|
||||
-- Уведомляем владельцев о захвате
|
||||
if (point.ownerFaction and point.ownerFaction != 0 and point.ownerFaction != faction) then
|
||||
local capturerName = ix.faction.Get(faction).name or "Противник"
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
local char = v:GetCharacter()
|
||||
if (char and char:GetFaction() == point.ownerFaction) then
|
||||
v:Notify("ВНИМАНИЕ! Ваша точка захватывается фракцией " .. capturerName .. "!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local serverlogsPlugin = ix.plugin.list["serverlogs"]
|
||||
if (serverlogsPlugin) then
|
||||
local factionName = ix.faction.Get(faction).name or tostring(faction)
|
||||
local pointPos = tostring(point:GetPos())
|
||||
local message = string.format("Фракция '%s' начала захват точки (позиция: %s)", factionName, pointPos)
|
||||
serverlogsPlugin:AddLog("CAPTURE_START", message, nil, {
|
||||
faction = faction,
|
||||
factionName = factionName,
|
||||
playerCount = playerCount,
|
||||
pointPosition = pointPos
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
point.capturingFaction = faction
|
||||
point.capturingPlayers = playerCount
|
||||
|
||||
local baseSpeed = 100 / self.captureBaseTime -- Progress per second
|
||||
local captureSpeed = baseSpeed * playerCount * FrameTime()
|
||||
|
||||
point.captureProgress = (point.captureProgress or 0) + captureSpeed
|
||||
|
||||
if (point.captureProgress >= 100) then
|
||||
point.captureProgress = 100
|
||||
local oldOwner = point.ownerFaction
|
||||
point.ownerFaction = faction
|
||||
point:SetOwnerFactionID(faction)
|
||||
point.capturingFaction = nil
|
||||
point.capturingPlayers = 0
|
||||
|
||||
self:GiveRewards(faction, players)
|
||||
self:SaveData()
|
||||
|
||||
-- Логируем успешный захват точки
|
||||
local serverlogsPlugin = ix.plugin.list["serverlogs"]
|
||||
if (serverlogsPlugin) then
|
||||
local factionName = ix.faction.Get(faction).name or tostring(faction)
|
||||
local pointPos = tostring(point:GetPos())
|
||||
local oldFactionData = oldOwner and ix.faction.Get(oldOwner)
|
||||
local oldFactionName = (oldFactionData and oldFactionData.name) or (oldOwner and tostring(oldOwner)) or "Нейтральная"
|
||||
local message = string.format("Точка захвачена фракцией '%s' (предыдущий владелец: %s)", factionName, oldFactionName)
|
||||
serverlogsPlugin:AddLog("CAPTURE_COMPLETE", message, nil, {
|
||||
faction = faction,
|
||||
factionName = factionName,
|
||||
oldOwner = oldOwner,
|
||||
oldOwnerName = oldFactionName,
|
||||
playerCount = playerCount,
|
||||
pointPosition = pointPos
|
||||
})
|
||||
end
|
||||
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
local char = ply:GetCharacter()
|
||||
if (char and char:GetFaction() == faction) then
|
||||
ply:Notify("Точка захвачена вашей фракцией!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Only sync if progress changed significantly, something started/stopped, or a timer passed
|
||||
local curTime = CurTime()
|
||||
point.nextSync = point.nextSync or 0
|
||||
|
||||
if (point.lastSyncProgress == nil or
|
||||
math.abs(point.captureProgress - point.lastSyncProgress) >= 1 or
|
||||
curTime >= point.nextSync) then
|
||||
|
||||
self:SyncCapturePoint(point)
|
||||
point.lastSyncProgress = point.captureProgress
|
||||
point.nextSync = curTime + 0.2 -- Sync at most 5 times per second
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:SyncCapturePoint(point)
|
||||
net.Start("ixCaptureSync")
|
||||
net.WriteEntity(point)
|
||||
net.WriteFloat(point.captureProgress or 0)
|
||||
net.WriteUInt(point.ownerFaction or 0, 8)
|
||||
net.WriteUInt(point.capturingFaction or 0, 8)
|
||||
net.WriteUInt(point.capturingPlayers or 0, 8)
|
||||
net.WriteBool(point.captureBlocked or false)
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
function PLUGIN:Think()
|
||||
if (!self.nextTickReward) then
|
||||
self.nextTickReward = CurTime() + (self.tickInterval or 300)
|
||||
elseif (CurTime() >= self.nextTickReward) then
|
||||
self:ProcessPeriodicRewards()
|
||||
self.nextTickReward = CurTime() + (self.tickInterval or 300)
|
||||
end
|
||||
|
||||
for _, point in ipairs(self.capturePoints) do
|
||||
if (IsValid(point)) then
|
||||
self:ProcessCapture(point)
|
||||
end
|
||||
end
|
||||
|
||||
if (!self.nextAutoSave or CurTime() >= self.nextAutoSave) then
|
||||
self:SaveData()
|
||||
self.nextAutoSave = CurTime() + 60
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:RegisterCapturePoint(point)
|
||||
table.insert(self.capturePoints, point)
|
||||
point.captureProgress = 0
|
||||
point.ownerFaction = nil
|
||||
point:SetOwnerFactionID(0)
|
||||
point.capturingFaction = nil
|
||||
point.capturingPlayers = 0
|
||||
end
|
||||
|
||||
function PLUGIN:EntityRemoved(entity)
|
||||
if (entity:GetClass() == "ix_capture_point") then
|
||||
for i, point in ipairs(self.capturePoints) do
|
||||
if (point == entity) then
|
||||
table.remove(self.capturePoints, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:SaveData()
|
||||
local data = {}
|
||||
|
||||
for _, point in ipairs(ents.FindByClass("ix_capture_point")) do
|
||||
if IsValid(point) then
|
||||
data[#data + 1] = {
|
||||
pos = point:GetPos(),
|
||||
angles = point:GetAngles(),
|
||||
ownerFaction = point.ownerFaction or 0,
|
||||
captureProgress = point.captureProgress or 0
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
self:SetData(data)
|
||||
end
|
||||
|
||||
function PLUGIN:LoadData()
|
||||
local data = self:GetData()
|
||||
|
||||
if (!data or #data == 0) then
|
||||
for _, v in ipairs(self.defaultPoints or {}) do
|
||||
local point = ents.Create("ix_capture_point")
|
||||
if IsValid(point) then
|
||||
point:SetPos(v.pos)
|
||||
point:SetAngles(v.ang)
|
||||
point:Spawn()
|
||||
|
||||
timer.Simple(0.1, function()
|
||||
if IsValid(point) then
|
||||
point.ownerFaction = 0
|
||||
point:SetOwnerFactionID(0)
|
||||
point.captureProgress = 0
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in ipairs(data) do
|
||||
local point = ents.Create("ix_capture_point")
|
||||
if IsValid(point) then
|
||||
point:SetPos(v.pos)
|
||||
point:SetAngles(v.angles)
|
||||
point:Spawn()
|
||||
|
||||
timer.Simple(0.1, function()
|
||||
if IsValid(point) then
|
||||
point.ownerFaction = v.ownerFaction
|
||||
point:SetOwnerFactionID(v.ownerFaction or 0)
|
||||
point.captureProgress = v.captureProgress or 0
|
||||
|
||||
self:SyncCapturePoint(point)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:OnUnloaded()
|
||||
self:SaveData()
|
||||
end
|
||||
|
||||
-- Подсчет захваченных точек каждой фракцией
|
||||
function PLUGIN:GetCapturedPointsCount()
|
||||
local counts = {}
|
||||
|
||||
for _, point in ipairs(self.capturePoints) do
|
||||
if IsValid(point) and point.ownerFaction and point.ownerFaction ~= 0 then
|
||||
counts[point.ownerFaction] = (counts[point.ownerFaction] or 0) + 1
|
||||
end
|
||||
end
|
||||
|
||||
return counts
|
||||
end
|
||||
|
||||
-- Периодическое начисление очков
|
||||
function PLUGIN:ProcessPeriodicRewards()
|
||||
local counts = self:GetCapturedPointsCount()
|
||||
|
||||
for faction, pointCount in pairs(counts) do
|
||||
if pointCount > 0 then
|
||||
local supplyReward = pointCount * self.tickSupplyPerPoint
|
||||
local vehicleReward = pointCount * self.tickVehiclePerPoint
|
||||
|
||||
if ix.plugin.list["arsenal"] then
|
||||
ix.plugin.list["arsenal"]:AddFactionSupply(faction, supplyReward)
|
||||
end
|
||||
|
||||
if ix.plugin.list["vehicles"] then
|
||||
ix.plugin.list["vehicles"]:AddFactionPoints(faction, vehicleReward)
|
||||
end
|
||||
|
||||
local factionName = ix.faction.Get(faction).name or "Фракция"
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
local char = ply:GetCharacter()
|
||||
if char and char:GetFaction() == faction then
|
||||
ply:Notify(string.format("Ваша фракция получила: +%d снабжения, +%d очков техники (точек: %d)",
|
||||
supplyReward, vehicleReward, pointCount))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.command.Add("CaptureSystemSave", {
|
||||
description = "Сохранить точки захвата",
|
||||
superAdminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
local plugin = ix.plugin.Get("capture")
|
||||
if (!plugin) then
|
||||
return "@commandNoExist"
|
||||
end
|
||||
|
||||
plugin:SaveData()
|
||||
local count = #ents.FindByClass("ix_capture_point")
|
||||
client:Notify("Точки захвата сохранены (" .. count .. " шт.)")
|
||||
end
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
local PLUGIN = PLUGIN
|
||||
PLUGIN.name = "Carrying Entities"
|
||||
PLUGIN.author = "Scripty"
|
||||
PLUGIN.description = "Позволяет игрокам поднимать любые энтити на клавишу E через стандартную механику."
|
||||
|
||||
-- Список классов, которые ЗАПРЕЩЕНО поднимать
|
||||
local BLACKLIST = {
|
||||
["player"] = true,
|
||||
["worldspawn"] = true,
|
||||
["func_door"] = true,
|
||||
["func_door_rotating"] = true,
|
||||
["prop_door_rotating"] = true,
|
||||
["func_movelinear"] = true,
|
||||
["prop_dynamic"] = true,
|
||||
["ix_vendor"] = true,
|
||||
["gmod_hands"] = true,
|
||||
["viewmodel"] = true
|
||||
}
|
||||
|
||||
if SERVER then
|
||||
-- Этот хук вызывается ядром Helix во время GM:AllowPlayerPickup
|
||||
function PLUGIN:CanPlayerPickupEntity(client, entity)
|
||||
if (!IsValid(entity) or entity:IsPlayer() or entity:IsWorld()) then
|
||||
return false
|
||||
end
|
||||
|
||||
local class = entity:GetClass()
|
||||
if (BLACKLIST[class]) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Проверка защиты Helix (Prop Protection)
|
||||
local char = client:GetCharacter()
|
||||
local owner = entity:GetNetVar("owner", 0)
|
||||
|
||||
-- Разрешаем владельцу, админу или если объект ничей
|
||||
if (owner == 0 or (char and owner == char:GetID()) or client:IsAdmin()) then
|
||||
local phys = entity:GetPhysicsObject()
|
||||
|
||||
-- Разрешаем подбирать только то, что имеет физику
|
||||
if (IsValid(phys) and phys:IsMoveable()) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Прямое разрешение для пакетов крови (если физика считается не-moveable)
|
||||
if (class == "bloodbag_medicmod") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
1331
garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatbox.lua
Normal file
1331
garrysmod/gamemodes/militaryrp/plugins/chatbox/derma/cl_chatbox.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,121 @@
|
||||
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
ix.gui.chatTabCustomize = self
|
||||
|
||||
self:SetTitle(L("chatNewTab"))
|
||||
self:SetSizable(true)
|
||||
self:SetSize(ScrW() * 0.5, ScrH() * 0.5)
|
||||
|
||||
self.settings = self:Add("ixSettings")
|
||||
self.settings:Dock(FILL)
|
||||
self.settings:SetSearchEnabled(true)
|
||||
self.settings:AddCategory(L("chatAllowedClasses"))
|
||||
|
||||
-- controls
|
||||
local controlsPanel = self:Add("Panel")
|
||||
controlsPanel:Dock(BOTTOM)
|
||||
controlsPanel:DockMargin(0, 4, 0, 0)
|
||||
controlsPanel:SetTall(32)
|
||||
|
||||
self.create = controlsPanel:Add("DButton")
|
||||
self.create:SetText(L("create"))
|
||||
self.create:SizeToContents()
|
||||
self.create:Dock(FILL)
|
||||
self.create:DockMargin(0, 0, 4, 0)
|
||||
self.create.DoClick = ix.util.Bind(self, self.CreateClicked)
|
||||
|
||||
local uncheckAll = controlsPanel:Add("DButton")
|
||||
uncheckAll:SetText(L("uncheckAll"))
|
||||
uncheckAll:SizeToContents()
|
||||
uncheckAll:Dock(RIGHT)
|
||||
uncheckAll.DoClick = function()
|
||||
self:SetAllValues(false)
|
||||
end
|
||||
|
||||
local checkAll = controlsPanel:Add("DButton")
|
||||
checkAll:SetText(L("checkAll"))
|
||||
checkAll:SizeToContents()
|
||||
checkAll:Dock(RIGHT)
|
||||
checkAll:DockMargin(0, 0, 4, 0)
|
||||
checkAll.DoClick = function()
|
||||
self:SetAllValues(true)
|
||||
end
|
||||
|
||||
-- chat class settings
|
||||
self.name = self.settings:AddRow(ix.type.string)
|
||||
self.name:SetText(L("chatTabName"))
|
||||
self.name:SetValue(L("chatNewTabTitle"))
|
||||
self.name:SetZPos(-1)
|
||||
|
||||
for k, _ in SortedPairs(ix.chat.classes) do
|
||||
local panel = self.settings:AddRow(ix.type.bool, L("chatAllowedClasses"))
|
||||
panel:SetText(k)
|
||||
panel:SetValue(true, true)
|
||||
end
|
||||
|
||||
self.settings:SizeToContents()
|
||||
self:Center()
|
||||
self:MakePopup()
|
||||
end
|
||||
|
||||
function PANEL:PopulateFromTab(name, filter)
|
||||
self.tab = name
|
||||
|
||||
self:SetTitle(L("chatCustomize"))
|
||||
self.create:SetText(L("update"))
|
||||
self.name:SetValue(name)
|
||||
|
||||
for _, v in ipairs(self.settings:GetRows()) do
|
||||
if (filter[v:GetText()]) then
|
||||
v:SetValue(false, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetAllValues(bValue)
|
||||
for _, v in ipairs(self.settings:GetRows()) do
|
||||
if (v == self.name) then
|
||||
continue
|
||||
end
|
||||
|
||||
v:SetValue(tobool(bValue), true)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:CreateClicked()
|
||||
local name = self.tab and self.tab or self.name:GetValue()
|
||||
|
||||
if (self.tab != self.name:GetValue() and PLUGIN:TabExists(self.name:GetValue())) then
|
||||
ix.util.Notify(L("chatTabExists"))
|
||||
return
|
||||
end
|
||||
|
||||
local filter = {}
|
||||
|
||||
for _, v in ipairs(self.settings:GetRows()) do
|
||||
-- we only want to add entries for classes we don't want shown
|
||||
if (!v:GetValue()) then
|
||||
filter[v:GetText()] = true
|
||||
end
|
||||
end
|
||||
|
||||
if (self.tab) then
|
||||
self:OnTabUpdated(name, filter, self.name:GetValue())
|
||||
else
|
||||
self:OnTabCreated(name, filter)
|
||||
end
|
||||
|
||||
self:Remove()
|
||||
end
|
||||
|
||||
function PANEL:OnTabCreated(id, filter)
|
||||
end
|
||||
|
||||
function PANEL:OnTabUpdated(id, filter, newID)
|
||||
end
|
||||
|
||||
vgui.Register("ixChatboxTabCustomize", PANEL, "DFrame")
|
||||
@@ -0,0 +1,205 @@
|
||||
-- Переопределение стилей чатбокса под общий дизайн Military RP
|
||||
|
||||
local PLUGIN = PLUGIN
|
||||
local gradient = surface.GetTextureID("vgui/gradient-u")
|
||||
local gradientUp = surface.GetTextureID("vgui/gradient-d")
|
||||
|
||||
-- Цвета в стиле HUD
|
||||
local Colors = {
|
||||
green = Color(84, 147, 90),
|
||||
dark_green = Color(52, 91, 60),
|
||||
darker_green = Color(35, 53, 29),
|
||||
gray = Color(193, 193, 193),
|
||||
gray_dark = Color(94, 94, 94),
|
||||
black = Color(0, 0, 0, 220),
|
||||
black_light = Color(0, 0, 0, 150),
|
||||
white = Color(255, 255, 255)
|
||||
}
|
||||
|
||||
-- Переопределяем стандартные функции рисования чатбокса
|
||||
local SKIN = derma.GetDefaultSkin()
|
||||
|
||||
-- Фон чатбокса
|
||||
function SKIN:PaintChatboxBackground(panel, width, height)
|
||||
-- Blur эффект
|
||||
ix.util.DrawBlur(panel, 8)
|
||||
|
||||
-- Основной фон с градиентом
|
||||
surface.SetDrawColor(Colors.black)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
if (panel:GetActive()) then
|
||||
-- Зелёный градиент сверху когда чат активен
|
||||
surface.SetDrawColor(Colors.dark_green.r, Colors.dark_green.g, Colors.dark_green.b, 120)
|
||||
surface.SetTexture(gradientUp)
|
||||
surface.DrawTexturedRect(0, panel.tabs.buttons:GetTall(), width, height * 0.25)
|
||||
end
|
||||
|
||||
-- Обводка в стиле HUD
|
||||
surface.SetDrawColor(Colors.darker_green)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
|
||||
-- Тонкая внутренняя обводка
|
||||
surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 60)
|
||||
surface.DrawOutlinedRect(1, 1, width - 2, height - 2)
|
||||
end
|
||||
|
||||
-- Кнопки табов
|
||||
function SKIN:PaintChatboxTabButton(panel, width, height)
|
||||
if (panel:GetActive()) then
|
||||
-- Активная вкладка - зелёный градиент
|
||||
surface.SetDrawColor(Colors.dark_green)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 100)
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
else
|
||||
-- Неактивная вкладка
|
||||
surface.SetDrawColor(Colors.black_light)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
-- Индикатор непрочитанных сообщений
|
||||
if (panel:GetUnread()) then
|
||||
surface.SetDrawColor(ColorAlpha(Color(200, 200, 50), Lerp(panel.unreadAlpha, 0, 120)))
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, 0, width, height - 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- Разделитель вкладок
|
||||
surface.SetDrawColor(Colors.darker_green)
|
||||
surface.DrawRect(width - 1, 0, 1, height)
|
||||
end
|
||||
|
||||
-- Панель табов
|
||||
function SKIN:PaintChatboxTabs(panel, width, height, alpha)
|
||||
-- Фон панели табов
|
||||
surface.SetDrawColor(Colors.black_light)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
-- Градиент вниз
|
||||
surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 150)
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, height * 0.5, width, height * 0.5)
|
||||
|
||||
local tab = panel:GetActiveTab()
|
||||
|
||||
if (tab) then
|
||||
local button = tab:GetButton()
|
||||
local x, _ = button:GetPos()
|
||||
|
||||
-- Линия под активной вкладкой (зелёная)
|
||||
surface.SetDrawColor(Colors.green)
|
||||
surface.DrawRect(x, height - 2, button:GetWide(), 2)
|
||||
|
||||
-- Обводка панели табов
|
||||
surface.SetDrawColor(Colors.darker_green)
|
||||
surface.DrawRect(0, height - 1, x, 1) -- слева
|
||||
surface.DrawRect(x + button:GetWide(), height - 1, width - x - button:GetWide(), 1) -- справа
|
||||
end
|
||||
end
|
||||
|
||||
-- Поле ввода текста
|
||||
function SKIN:PaintChatboxEntry(panel, width, height)
|
||||
-- Фон поля ввода
|
||||
surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 100)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
-- Текст
|
||||
panel:DrawTextEntryText(Colors.white, Colors.green, Colors.white)
|
||||
|
||||
-- Обводка
|
||||
surface.SetDrawColor(Colors.dark_green)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
|
||||
-- Внутренний highlight когда активно
|
||||
if panel:HasFocus() then
|
||||
surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 80)
|
||||
surface.DrawOutlinedRect(1, 1, width - 2, height - 2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Превью команды
|
||||
function SKIN:DrawChatboxPreviewBox(x, y, text, color)
|
||||
color = color or Colors.green
|
||||
|
||||
local textWidth, textHeight = surface.GetTextSize(text)
|
||||
local width, height = textWidth + 8, textHeight + 8
|
||||
|
||||
-- Фон
|
||||
surface.SetDrawColor(Colors.dark_green)
|
||||
surface.DrawRect(x, y, width, height)
|
||||
|
||||
-- Градиент
|
||||
surface.SetDrawColor(color.r, color.g, color.b, 60)
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(x, y, width, height)
|
||||
|
||||
-- Текст
|
||||
surface.SetTextColor(Colors.white)
|
||||
surface.SetTextPos(x + width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5)
|
||||
surface.DrawText(text)
|
||||
|
||||
-- Обводка
|
||||
surface.SetDrawColor(Colors.darker_green)
|
||||
surface.DrawOutlinedRect(x, y, width, height)
|
||||
|
||||
return width
|
||||
end
|
||||
|
||||
-- Префикс команды
|
||||
function SKIN:DrawChatboxPrefixBox(panel, width, height)
|
||||
local color = panel:GetBackgroundColor() or Colors.green
|
||||
|
||||
-- Фон
|
||||
surface.SetDrawColor(Colors.dark_green)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
-- Градиент
|
||||
surface.SetDrawColor(color.r, color.g, color.b, 80)
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
|
||||
-- Обводка
|
||||
surface.SetDrawColor(Colors.darker_green)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
-- Автодополнение команд
|
||||
function SKIN:PaintChatboxAutocompleteEntry(panel, width, height)
|
||||
-- Выделенный фон
|
||||
if (panel.highlightAlpha > 0) then
|
||||
surface.SetDrawColor(Colors.dark_green.r, Colors.dark_green.g, Colors.dark_green.b, panel.highlightAlpha * 150)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
-- Градиент выделения
|
||||
surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, panel.highlightAlpha * 60)
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
-- Разделитель между командами
|
||||
surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 100)
|
||||
surface.DrawRect(0, height - 1, width, 1)
|
||||
end
|
||||
|
||||
hook.Add("LoadFonts", "MilitaryRPChatFonts", function()
|
||||
-- Переопределяем шрифты чата на Montserrat
|
||||
local scale = ix.option.Get("chatFontScale", 1)
|
||||
|
||||
surface.CreateFont("ixChatFont", {
|
||||
font = "Montserrat",
|
||||
size = 17 * scale,
|
||||
weight = 400,
|
||||
extended = true
|
||||
})
|
||||
|
||||
surface.CreateFont("ixChatFontItalics", {
|
||||
font = "Montserrat",
|
||||
size = 17 * scale,
|
||||
weight = 400,
|
||||
italic = true,
|
||||
extended = true
|
||||
})
|
||||
end)
|
||||
@@ -0,0 +1,25 @@
|
||||
NAME = "English"
|
||||
|
||||
LANGUAGE = {
|
||||
-- Category
|
||||
optChat = "Chat",
|
||||
|
||||
-- Settings
|
||||
optChatNotices = "Chat Notices",
|
||||
optChatNoticesDesc = "Show system notifications in chat",
|
||||
|
||||
optChatTimestamps = "Timestamps",
|
||||
optChatTimestampsDesc = "Display message send time",
|
||||
|
||||
optChatFontScale = "Font Scale",
|
||||
optChatFontScaleDesc = "Chat font scale (0.1-2)",
|
||||
|
||||
optChatOutline = "Text Outline",
|
||||
optChatOutlineDesc = "Add outline to chat text for better readability",
|
||||
|
||||
optChatTabs = "Chat Tabs",
|
||||
optChatTabsDesc = "Chat tabs configuration (JSON)",
|
||||
|
||||
optChatPosition = "Chat Position",
|
||||
optChatPositionDesc = "Chat window position and size (JSON)",
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
NAME = "Русский"
|
||||
|
||||
LANGUAGE = {
|
||||
-- Category
|
||||
optChat = "Чат",
|
||||
|
||||
-- Settings
|
||||
optChatNotices = "Уведомления в чате",
|
||||
optChatNoticesDesc = "Показывать системные уведомления в чате",
|
||||
|
||||
optChatTimestamps = "Временные метки",
|
||||
optChatTimestampsDesc = "Отображать время отправки сообщений",
|
||||
|
||||
optChatFontScale = "Размер шрифта",
|
||||
optChatFontScaleDesc = "Масштаб шрифта в чате (0.1-2)",
|
||||
|
||||
optChatOutline = "Обводка текста",
|
||||
optChatOutlineDesc = "Добавить обводку к тексту чата для лучшей читаемости",
|
||||
|
||||
optChatTabs = "Вкладки чата",
|
||||
optChatTabsDesc = "Конфигурация вкладок чата (JSON)",
|
||||
|
||||
optChatPosition = "Позиция чата",
|
||||
optChatPositionDesc = "Позиция и размер окна чата (JSON)",
|
||||
}
|
||||
188
garrysmod/gamemodes/militaryrp/plugins/chatbox/sh_plugin.lua
Normal file
188
garrysmod/gamemodes/militaryrp/plugins/chatbox/sh_plugin.lua
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
PLUGIN.name = "Chatbox"
|
||||
PLUGIN.author = "`impulse"
|
||||
PLUGIN.description = "Replaces the chatbox to enable customization, autocomplete, and useful info."
|
||||
|
||||
if (CLIENT) then
|
||||
ix.util.Include("derma/cl_chatskin.lua")
|
||||
|
||||
ix.chat.history = ix.chat.history or {} -- array of strings the player has entered into the chatbox
|
||||
ix.chat.currentCommand = ""
|
||||
ix.chat.currentArguments = {}
|
||||
|
||||
ix.option.Add("chatNotices", ix.type.bool, false, {
|
||||
category = "optChat",
|
||||
name = "optChatNotices",
|
||||
description = "optChatNoticesDesc"
|
||||
})
|
||||
|
||||
ix.option.Add("chatTimestamps", ix.type.bool, false, {
|
||||
category = "optChat",
|
||||
name = "optChatTimestamps",
|
||||
description = "optChatTimestampsDesc"
|
||||
})
|
||||
|
||||
ix.option.Add("chatFontScale", ix.type.number, 1, {
|
||||
category = "optChat",
|
||||
name = "optChatFontScale",
|
||||
description = "optChatFontScaleDesc",
|
||||
min = 0.1, max = 2, decimals = 2,
|
||||
OnChanged = function()
|
||||
hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont"))
|
||||
PLUGIN:CreateChat()
|
||||
end
|
||||
})
|
||||
|
||||
ix.option.Add("chatOutline", ix.type.bool, false, {
|
||||
category = "optChat",
|
||||
name = "optChatOutline",
|
||||
description = "optChatOutlineDesc"
|
||||
})
|
||||
|
||||
-- tabs and their respective filters
|
||||
ix.option.Add("chatTabs", ix.type.string, "", {
|
||||
category = "optChat",
|
||||
name = "optChatTabs",
|
||||
description = "optChatTabsDesc",
|
||||
hidden = function()
|
||||
return true
|
||||
end
|
||||
})
|
||||
|
||||
-- chatbox size and position
|
||||
ix.option.Add("chatPosition", ix.type.string, "", {
|
||||
category = "optChat",
|
||||
name = "optChatPosition",
|
||||
description = "optChatPositionDesc",
|
||||
hidden = function()
|
||||
return true
|
||||
end
|
||||
})
|
||||
|
||||
function PLUGIN:CreateChat()
|
||||
if (IsValid(self.panel)) then
|
||||
self.panel:Remove()
|
||||
end
|
||||
|
||||
self.panel = vgui.Create("ixChatbox")
|
||||
self.panel:SetupTabs(util.JSONToTable(ix.option.Get("chatTabs", "")))
|
||||
self.panel:SetupPosition(util.JSONToTable(ix.option.Get("chatPosition", "")))
|
||||
|
||||
hook.Run("ChatboxCreated")
|
||||
end
|
||||
|
||||
function PLUGIN:TabExists(id)
|
||||
if (!IsValid(self.panel)) then
|
||||
return false
|
||||
end
|
||||
|
||||
return self.panel.tabs:GetTabs()[id] != nil
|
||||
end
|
||||
|
||||
function PLUGIN:SaveTabs()
|
||||
local tabs = {}
|
||||
|
||||
for id, panel in pairs(self.panel.tabs:GetTabs()) do
|
||||
tabs[id] = panel:GetFilter()
|
||||
end
|
||||
|
||||
ix.option.Set("chatTabs", util.TableToJSON(tabs))
|
||||
end
|
||||
|
||||
function PLUGIN:SavePosition()
|
||||
local x, y = self.panel:GetPos()
|
||||
local width, height = self.panel:GetSize()
|
||||
|
||||
ix.option.Set("chatPosition", util.TableToJSON({x, y, width, height}))
|
||||
end
|
||||
|
||||
function PLUGIN:InitPostEntity()
|
||||
self:CreateChat()
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerBindPress(client, bind, pressed)
|
||||
bind = bind:lower()
|
||||
|
||||
if (bind:find("messagemode") and pressed) then
|
||||
-- Запрещаем открывать чат если игрок мёртв
|
||||
if !LocalPlayer():Alive() then
|
||||
return true
|
||||
end
|
||||
|
||||
self.panel:SetActive(true)
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:OnPauseMenuShow()
|
||||
if (!IsValid(ix.gui.chat) or !ix.gui.chat:GetActive()) then
|
||||
return
|
||||
end
|
||||
|
||||
ix.gui.chat:SetActive(false)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function PLUGIN:HUDShouldDraw(element)
|
||||
if (element == "CHudChat") then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:ScreenResolutionChanged(oldWidth, oldHeight)
|
||||
self:CreateChat()
|
||||
end
|
||||
|
||||
function PLUGIN:ChatText(index, name, text, messageType)
|
||||
if (messageType == "none" and IsValid(self.panel)) then
|
||||
self.panel:AddMessage(text)
|
||||
end
|
||||
end
|
||||
|
||||
-- luacheck: globals chat
|
||||
chat.ixAddText = chat.ixAddText or chat.AddText
|
||||
|
||||
function chat.AddText(...)
|
||||
if (IsValid(PLUGIN.panel)) then
|
||||
PLUGIN.panel:AddMessage(...)
|
||||
end
|
||||
|
||||
-- log chat message to console
|
||||
local text = {}
|
||||
|
||||
for _, v in ipairs({...}) do
|
||||
if (istable(v) or isstring(v)) then
|
||||
text[#text + 1] = v
|
||||
elseif (isentity(v) and v:IsPlayer()) then
|
||||
text[#text + 1] = team.GetColor(v:Team())
|
||||
text[#text + 1] = v:Name()
|
||||
elseif (type(v) != "IMaterial") then
|
||||
text[#text + 1] = tostring(v)
|
||||
end
|
||||
end
|
||||
|
||||
text[#text + 1] = "\n"
|
||||
MsgC(unpack(text))
|
||||
end
|
||||
else
|
||||
util.AddNetworkString("ixChatMessage")
|
||||
|
||||
net.Receive("ixChatMessage", function(length, client)
|
||||
local text = net.ReadString()
|
||||
|
||||
if ((client.ixNextChat or 0) < CurTime() and isstring(text) and text:find("%S")) then
|
||||
local maxLength = ix.config.Get("chatMax")
|
||||
|
||||
if (text:utf8len() > maxLength) then
|
||||
text = text:utf8sub(0, maxLength)
|
||||
end
|
||||
|
||||
hook.Run("PlayerSay", client, text)
|
||||
client.ixNextChat = CurTime() + 0.5
|
||||
end
|
||||
end)
|
||||
end
|
||||
158
garrysmod/gamemodes/militaryrp/plugins/chatcontrol/sh_plugin.lua
Normal file
158
garrysmod/gamemodes/militaryrp/plugins/chatcontrol/sh_plugin.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
PLUGIN.name = "Chat Control"
|
||||
PLUGIN.author = "RefoselDev"
|
||||
PLUGIN.description = "Удаление ненужных типов чатов"
|
||||
|
||||
|
||||
hook.Add("InitializedChatClasses", "ixChatControl.RemoveChats", function()
|
||||
ix.chat.classes.it = nil
|
||||
ix.chat.classes.connect = nil
|
||||
ix.chat.classes.disconnect = nil
|
||||
ix.chat.classes.event = nil
|
||||
|
||||
ix.chat.Register("rp", {
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
local name = IsValid(speaker) and speaker:Name() or "Console"
|
||||
chat.AddText(Color(26, 221, 0), "[RP] ", name, ": ", text)
|
||||
end,
|
||||
prefix = {"/rp", "/RP"},
|
||||
description = "Чат РП отыгровок",
|
||||
color = Color(150, 200, 255),
|
||||
CanHear = function(self, speaker, listener)
|
||||
return listener:GetCharacter() != nil
|
||||
end,
|
||||
deadCanChat = false
|
||||
})
|
||||
|
||||
-- Чат между фракциями
|
||||
ix.chat.Register("adgl", {
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
local name = IsValid(speaker) and speaker:Name() or "Console"
|
||||
local prefix = "[ФРАКЦИЯ] "
|
||||
local prefixColor = Color(255, 200, 100)
|
||||
|
||||
if (IsValid(speaker) and speaker:GetCharacter()) then
|
||||
local faction = speaker:GetCharacter():GetFaction()
|
||||
|
||||
if (faction == FACTION_RUSSIAN) then
|
||||
prefix = "[ВС РФ → ВСУ] "
|
||||
prefixColor = Color(255, 196, 0)
|
||||
elseif (faction == FACTION_UKRAINE) then
|
||||
prefix = "[ВСУ → ВС РФ] "
|
||||
prefixColor = Color(255, 196, 0)
|
||||
end
|
||||
end
|
||||
|
||||
chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text)
|
||||
end,
|
||||
prefix = {"/adgl"},
|
||||
description = "Чат между фракциями",
|
||||
color = Color(255, 200, 100),
|
||||
CanHear = function(self, speaker, listener)
|
||||
-- Слышат только игроки с персонажем и определенной фракцией
|
||||
if (!listener:GetCharacter()) then
|
||||
return false
|
||||
end
|
||||
|
||||
local listenerFaction = listener:GetCharacter():GetFaction()
|
||||
return listenerFaction == FACTION_RUSSIAN or listenerFaction == FACTION_UKRAINE
|
||||
end,
|
||||
deadCanChat = false
|
||||
})
|
||||
|
||||
-- Объявления стороны
|
||||
ix.chat.Register("ad", {
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
local name = IsValid(speaker) and speaker:Name() or "Console"
|
||||
local prefix = "[ОБЪЯВЛЕНИЕ] "
|
||||
local prefixColor = Color(200, 200, 200)
|
||||
|
||||
if (IsValid(speaker) and speaker:GetCharacter()) then
|
||||
local faction = speaker:GetCharacter():GetFaction()
|
||||
|
||||
if (faction == FACTION_RUSSIAN) then
|
||||
prefix = "[ВС РФ] "
|
||||
prefixColor = Color(255, 100, 100)
|
||||
elseif (faction == FACTION_UKRAINE) then
|
||||
prefix = "[ВСУ] "
|
||||
prefixColor = Color(255, 196, 0)
|
||||
end
|
||||
end
|
||||
|
||||
chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text)
|
||||
end,
|
||||
prefix = {"/ad"},
|
||||
description = "Объявления стороны",
|
||||
color = Color(200, 200, 200),
|
||||
CanHear = function(self, speaker, listener)
|
||||
-- Слышат только игроки той же фракции
|
||||
if (!listener:GetCharacter() or !speaker:GetCharacter()) then
|
||||
return false
|
||||
end
|
||||
|
||||
local speakerFaction = speaker:GetCharacter():GetFaction()
|
||||
local listenerFaction = listener:GetCharacter():GetFaction()
|
||||
|
||||
return speakerFaction == listenerFaction
|
||||
end,
|
||||
deadCanChat = false
|
||||
})
|
||||
|
||||
-- Чат подразделения
|
||||
ix.chat.Register("podr", {
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
local name = IsValid(speaker) and speaker:Name() or "Console"
|
||||
local prefix = "[ПОДРАЗДЕЛЕНИЕ] "
|
||||
local prefixColor = Color(100, 150, 255)
|
||||
|
||||
if (IsValid(speaker) and speaker:GetCharacter()) then
|
||||
local character = speaker:GetCharacter()
|
||||
local faction = character:GetFaction()
|
||||
local podrID = (character.GetPodr and character:GetPodr()) or 1
|
||||
local factionTable = ix.faction.indices[faction]
|
||||
|
||||
if (factionTable and factionTable.Podr and factionTable.Podr[podrID]) then
|
||||
local podrName = factionTable.Podr[podrID].name or "Неизвестно"
|
||||
prefix = "["..podrName.."] "
|
||||
|
||||
if (faction == FACTION_RUSSIAN) then
|
||||
prefixColor = Color(255, 196, 0)
|
||||
elseif (faction == FACTION_UKRAINE) then
|
||||
prefixColor = Color(255, 196, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text)
|
||||
end,
|
||||
prefix = {"/podr", "/p"},
|
||||
description = "Чат подразделения",
|
||||
color = Color(100, 150, 255),
|
||||
CanHear = function(self, speaker, listener)
|
||||
-- Слышат только игроки той же фракции и того же подразделения
|
||||
if (!listener:GetCharacter() or !speaker:GetCharacter()) then
|
||||
return false
|
||||
end
|
||||
|
||||
local speakerChar = speaker:GetCharacter()
|
||||
local listenerChar = listener:GetCharacter()
|
||||
|
||||
local speakerFaction = speakerChar:GetFaction()
|
||||
local listenerFaction = listenerChar:GetFaction()
|
||||
|
||||
if (speakerFaction != listenerFaction) then
|
||||
return false
|
||||
end
|
||||
|
||||
local speakerPodr = (speakerChar.GetPodr and speakerChar:GetPodr()) or 1
|
||||
local listenerPodr = (listenerChar.GetPodr and listenerChar:GetPodr()) or 1
|
||||
|
||||
return speakerPodr == listenerPodr
|
||||
end,
|
||||
deadCanChat = false
|
||||
})
|
||||
|
||||
if (SERVER) then
|
||||
print("[Chat Control] Удалены чаты: it, connect, disconnect, event")
|
||||
print("[Chat Control] Добавлены чаты: /rp, /adgl, /ad, /podr")
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,209 @@
|
||||
-- Создание шрифтов
|
||||
surface.CreateFont("ixDeathScreenMain", {
|
||||
font = "OperiusRegular",
|
||||
size = 180,
|
||||
weight = 300,
|
||||
extended = true
|
||||
})
|
||||
|
||||
surface.CreateFont("ixDeathScreenSub", {
|
||||
font = "OperiusRegular",
|
||||
size = 90,
|
||||
weight = 300,
|
||||
extended = true
|
||||
})
|
||||
|
||||
-- Резервный шрифт если OperiusRegular недоступен
|
||||
surface.CreateFont("ixDeathScreenMainFallback", {
|
||||
font = "Roboto",
|
||||
size = 180,
|
||||
weight = 700,
|
||||
extended = true
|
||||
})
|
||||
|
||||
surface.CreateFont("ixDeathScreenSubFallback", {
|
||||
font = "Roboto",
|
||||
size = 90,
|
||||
weight = 500,
|
||||
extended = true
|
||||
})
|
||||
|
||||
surface.CreateFont("ixDeathScreenSmall", {
|
||||
font = "Roboto",
|
||||
size = 40,
|
||||
weight = 400,
|
||||
extended = true
|
||||
})
|
||||
|
||||
-- Проверка доступности шрифта
|
||||
local function GetFont(primary, fallback)
|
||||
local w, h = surface.GetTextSize("TEST")
|
||||
if w > 0 then
|
||||
return primary
|
||||
end
|
||||
return fallback
|
||||
end
|
||||
|
||||
local deathScreenPanel = nil
|
||||
|
||||
-- Получение экрана смерти от сервера
|
||||
net.Receive("ixDeathScreen", function()
|
||||
if not ix.config.Get("deathScreenEnabled") then
|
||||
return
|
||||
end
|
||||
|
||||
-- Закрываем старую панель если существует
|
||||
if IsValid(deathScreenPanel) then
|
||||
deathScreenPanel:Remove()
|
||||
end
|
||||
|
||||
local duration = net.ReadFloat()
|
||||
local minDuration = net.ReadFloat()
|
||||
local killerName = net.ReadString()
|
||||
|
||||
deathScreenPanel = vgui.Create("DFrame")
|
||||
deathScreenPanel:SetSize(ScrW(), ScrH())
|
||||
deathScreenPanel:SetPos(0, 0)
|
||||
deathScreenPanel:SetTitle("")
|
||||
deathScreenPanel:ShowCloseButton(false)
|
||||
deathScreenPanel:SetDraggable(false)
|
||||
deathScreenPanel:MakePopup()
|
||||
deathScreenPanel:SetKeyboardInputEnabled(false)
|
||||
deathScreenPanel:SetMouseInputEnabled(false)
|
||||
|
||||
local colorAlpha = 0
|
||||
local textAlpha = 0
|
||||
local fadeOutStarted = false
|
||||
local startTime = RealTime()
|
||||
local canRespawnTime = startTime + minDuration
|
||||
|
||||
local fadeInSpeed = ix.config.Get("deathScreenFadeInSpeed", 5)
|
||||
local textFadeSpeed = ix.config.Get("deathScreenTextFadeSpeed", 0.5)
|
||||
local textColor = ix.config.Get("deathScreenTextColor", Color(132, 43, 60))
|
||||
local mainText = ix.config.Get("deathScreenMainText", "YOU'RE DEAD")
|
||||
local respawnText = ix.config.Get("deathScreenRespawnText", "НАЖМИТЕ [ПРОБЕЛ] ЧТОБЫ ВОЗРОДИТЬСЯ")
|
||||
local subText = "[Убил: " .. killerName .. "]"
|
||||
|
||||
-- Убраны таймеры автоматического закрытия
|
||||
|
||||
-- Обработка нажатия пробела
|
||||
deathScreenPanel.Think = function(s)
|
||||
if (fadeOutStarted) then return end
|
||||
|
||||
local lp = LocalPlayer()
|
||||
if (!IsValid(lp)) then return end
|
||||
|
||||
-- Если игрока возродили (медик или админ), убираем экран
|
||||
-- Проверяем только спустя 2 секунды, чтобы избежать багов при переходе в режим смерти
|
||||
if (RealTime() > startTime + 2) then
|
||||
if (lp:Alive() and !lp:GetNWBool("HeartAttack", false)) then
|
||||
fadeOutStarted = true
|
||||
timer.Simple(0.5, function()
|
||||
if (IsValid(s)) then s:Remove() end
|
||||
end)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Проверка возможности сдаться
|
||||
local character = lp:GetCharacter()
|
||||
local minBalance = ix.config.Get("deathRespawnMinBalance", 5000)
|
||||
local canAfford = character and character:GetMoney() >= minBalance
|
||||
|
||||
if (canAfford and RealTime() >= canRespawnTime and input.IsKeyDown(KEY_SPACE)) then
|
||||
net.Start("ixPlayerRespawn")
|
||||
net.SendToServer()
|
||||
|
||||
fadeOutStarted = true
|
||||
timer.Simple(0.5, function()
|
||||
if (IsValid(s)) then s:Remove() end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
deathScreenPanel.Paint = function(s, w, h)
|
||||
if fadeOutStarted then
|
||||
colorAlpha = math.Approach(colorAlpha, 0, FrameTime() * 255)
|
||||
else
|
||||
colorAlpha = math.Approach(colorAlpha, 255, FrameTime() * fadeInSpeed * 255)
|
||||
end
|
||||
|
||||
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, colorAlpha))
|
||||
end
|
||||
|
||||
local textLabel = vgui.Create("DLabel", deathScreenPanel)
|
||||
textLabel:SetSize(deathScreenPanel:GetWide(), deathScreenPanel:GetTall())
|
||||
textLabel:SetPos(0, 0)
|
||||
textLabel:SetText("")
|
||||
|
||||
textLabel.Paint = function(s, w, h)
|
||||
local elapsed = RealTime() - startTime
|
||||
|
||||
if fadeOutStarted then
|
||||
textAlpha = math.Approach(textAlpha, 0, FrameTime() * 255 * 4)
|
||||
elseif elapsed > 1 then
|
||||
textAlpha = math.Approach(textAlpha, 255, FrameTime() * textFadeSpeed * 255)
|
||||
end
|
||||
|
||||
-- Определяем доступные шрифты
|
||||
local mainFont = GetFont("ixDeathScreenMain", "ixDeathScreenMainFallback")
|
||||
local subFont = GetFont("ixDeathScreenSub", "ixDeathScreenSubFallback")
|
||||
|
||||
-- Рисуем текст
|
||||
draw.SimpleText(mainText, mainFont, w/2, h/2,
|
||||
Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText(subText, subFont, w/2, h/2 + 144,
|
||||
Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Рисуем оставшееся время или подсказку
|
||||
local character = LocalPlayer():GetCharacter()
|
||||
local minBalance = ix.config.Get("deathRespawnMinBalance", 5000)
|
||||
local canAfford = character and character:GetMoney() >= minBalance
|
||||
|
||||
if (RealTime() < canRespawnTime) then
|
||||
local remaining = math.max(0, math.ceil(canRespawnTime - RealTime()))
|
||||
draw.SimpleText("ВОЗРОЖДЕНИЕ ЧЕРЕЗ: " .. remaining, subFont, w/2, h/2 + 250,
|
||||
Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
elseif (canAfford) then
|
||||
draw.SimpleText(respawnText, subFont, w/2, h/2 + 250,
|
||||
Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
else
|
||||
draw.SimpleText("НЕДОСТАТОЧНО СРЕДСТВ ДЛЯ СДАЧИ (" .. ix.currency.Get(minBalance) .. ")", subFont, w/2, h/2 + 250,
|
||||
Color(200, 50, 50, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
-- Рисуем предупреждение о штрафе
|
||||
local penalty = ix.config.Get("deathPenaltyAmount", 3000)
|
||||
if (penalty > 0) then
|
||||
local warningText = string.format(ix.config.Get("deathPenaltyWarningText", "При сдаче с вас спишут штраф: %s"), ix.currency.Get(penalty))
|
||||
draw.SimpleText(warningText, "ixDeathScreenSmall", w/2, h/2 + 330,
|
||||
Color(textColor.r, textColor.g, textColor.b, textAlpha * 0.8), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Очистка при отключении
|
||||
hook.Add("InitPostEntity", "ixDeathScreenCleanup", function()
|
||||
if IsValid(deathScreenPanel) then
|
||||
deathScreenPanel:Remove()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Камера следует за регдоллом при смерти
|
||||
hook.Add("CalcView", "ixDeathScreenView", function(ply, pos, angles, fov)
|
||||
if (IsValid(deathScreenPanel) and !ply:Alive()) then
|
||||
local ragdoll = ply:GetRagdollEntity()
|
||||
if (IsValid(ragdoll)) then
|
||||
local eyes = ragdoll:GetAttachment(ragdoll:LookupAttachment("eyes"))
|
||||
if (eyes) then
|
||||
return {
|
||||
origin = eyes.Pos,
|
||||
angles = eyes.Ang,
|
||||
fov = fov
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,138 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
PLUGIN.name = "Murder Drones Death Screen"
|
||||
PLUGIN.description = "Добавляет кинематографичный экран смерти в стиле Murder Drones"
|
||||
PLUGIN.author = "Scripty & Ported to Helix"
|
||||
|
||||
-- Конфигурация
|
||||
ix.config.Add("deathScreenEnabled", true, "Включить экран смерти Murder Drones", nil, {
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenDuration", 300, "Длительность показа экрана смерти (секунды)", nil, {
|
||||
data = {min = 1, max = 600, decimals = 1},
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenMinDuration", 20, "Минимальное время до возможности возродиться (секунды)", nil, {
|
||||
data = {min = 0, max = 60, decimals = 1},
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenFadeInSpeed", 5, "Скорость появления экрана", nil, {
|
||||
data = {min = 1, max = 20, decimals = 1},
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenTextFadeSpeed", 0.5, "Скорость появления текста", nil, {
|
||||
data = {min = 0.1, max = 5, decimals = 1},
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenTextColor", Color(132, 43, 60), "Цвет текста экрана смерти", nil, {
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenMainText", "YOU'RE DEAD", "Основной текст экрана смерти", nil, {
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenSubText", "[IDIOT]", "Подзаголовок экрана смерти", nil, {
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenRespawnText", "НАЖМИТЕ [ПРОБЕЛ] ЧТОБЫ ВОЗРОДИТЬСЯ", "Текст подсказки для возрождения", nil, {
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathScreenDisableSound", true, "Отключить стандартный звук смерти", nil, {
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathPenaltyAmount", 3000, "Сумма штрафа за возрождение", nil, {
|
||||
data = {min = 0, max = 100000},
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathPenaltyWarningText", "При сдаче с вас спишут штраф: %s", "Текст предупреждения о штрафе", nil, {
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
ix.config.Add("deathRespawnMinBalance", 5000, "Минимальный баланс для возможности сдаться", nil, {
|
||||
data = {min = 0, max = 100000},
|
||||
category = "Death Screen"
|
||||
})
|
||||
|
||||
if SERVER then
|
||||
util.AddNetworkString("ixDeathScreen")
|
||||
util.AddNetworkString("ixPlayerRespawn")
|
||||
|
||||
function PLUGIN:PlayerDeath(ply, inflictor, attacker)
|
||||
if not ix.config.Get("deathScreenEnabled") then
|
||||
return
|
||||
end
|
||||
|
||||
local maxDuration = ix.config.Get("deathScreenDuration", 300)
|
||||
local minDuration = ix.config.Get("deathScreenMinDuration", 20)
|
||||
local killerName = "Неизвестно"
|
||||
|
||||
if (IsValid(attacker)) then
|
||||
if (attacker:IsPlayer()) then
|
||||
killerName = attacker:Name()
|
||||
elseif (attacker:IsNPC()) then
|
||||
killerName = attacker:GetClass()
|
||||
elseif (IsValid(attacker:GetOwner()) and attacker:GetOwner():IsPlayer()) then
|
||||
killerName = attacker:GetOwner():Name()
|
||||
elseif (attacker.GetCreator and IsValid(attacker:GetCreator()) and attacker:GetCreator():IsPlayer()) then
|
||||
killerName = attacker:GetCreator():Name()
|
||||
else
|
||||
killerName = (attacker:GetClass() != "worldspawn") and attacker:GetClass() or "Окружение"
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("ixDeathScreen")
|
||||
net.WriteFloat(maxDuration)
|
||||
net.WriteFloat(minDuration)
|
||||
net.WriteString(killerName)
|
||||
net.Send(ply)
|
||||
|
||||
-- Блокируем стандартный спавн Helix
|
||||
local duration = RespawnTime[ply:GetUserGroup()] or 60
|
||||
ply.ixDeathTime = RealTime()
|
||||
ply.ixNextSpawn = RealTime() + duration
|
||||
ply:SetNetVar("deathTime", CurTime() + maxDuration)
|
||||
|
||||
-- Сохраняем реальное минимальное время для проверки
|
||||
ply.ixDeathScreenRespawnTime = CurTime() + minDuration
|
||||
end
|
||||
|
||||
-- Разрешаем создание стандартного регдолла Helix
|
||||
function PLUGIN:ShouldSpawnClientRagdoll(ply)
|
||||
return true
|
||||
end
|
||||
|
||||
net.Receive("ixPlayerRespawn", function(len, ply)
|
||||
if (IsValid(ply) and !ply:Alive() and ply:GetCharacter()) then
|
||||
if ((ply.ixDeathScreenRespawnTime or 0) <= CurTime()) then
|
||||
local character = ply:GetCharacter()
|
||||
local minBalance = ix.config.Get("deathRespawnMinBalance", 5000)
|
||||
|
||||
-- Проверка баланса перед спавном
|
||||
if (character:GetMoney() < minBalance) then
|
||||
ply:Notify("У вас недостаточно средств, чтобы сдаться. Нужно минимум " .. ix.currency.Get(minBalance))
|
||||
return
|
||||
end
|
||||
|
||||
local penalty = ix.config.Get("deathPenaltyAmount", 3000)
|
||||
if (penalty > 0) then
|
||||
character:TakeMoney(penalty)
|
||||
ply:Notify("Вы сдались. С вашего счета списано " .. ix.currency.Get(penalty) .. " штрафа.")
|
||||
end
|
||||
|
||||
ply:Spawn()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
ix.util.Include("cl_deathscreen.lua")
|
||||
@@ -0,0 +1,11 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
-- Просмотр украденного документа
|
||||
net.Receive("ixDisguise_ViewStolenID", function()
|
||||
local data = net.ReadTable()
|
||||
|
||||
local plugin = ix.plugin.Get("military_id")
|
||||
if plugin then
|
||||
plugin:ShowDocument(data)
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,83 @@
|
||||
ITEM.name = "Украденный военный билет"
|
||||
ITEM.description = "Военный билет, снятый с убитого противника. Содержит его данные."
|
||||
ITEM.model = "models/props_c17/paper01.mdl"
|
||||
ITEM.width = 1
|
||||
ITEM.height = 1
|
||||
ITEM.category = "Маскировка"
|
||||
ITEM.noDrop = false
|
||||
|
||||
-- Функция предъявления документа
|
||||
ITEM.functions.Show = {
|
||||
name = "Предъявить",
|
||||
tip = "Показать военный билет ближайшему игроку",
|
||||
icon = "icon16/eye.png",
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
|
||||
if CLIENT then
|
||||
-- Создаем меню выбора игрока
|
||||
local menu = DermaMenu()
|
||||
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if ply ~= client and ply:GetPos():Distance(client:GetPos()) <= 200 then
|
||||
menu:AddOption(ply:Name(), function()
|
||||
net.Start("ixDisguise_ShowStolenID")
|
||||
net.WriteUInt(ply:UserID(), 16)
|
||||
net.WriteUInt(item:GetID(), 32)
|
||||
net.SendToServer()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
if menu:ChildCount() == 0 then
|
||||
menu:AddOption("Нет игроков поблизости", function() end):SetDisabled(true)
|
||||
end
|
||||
|
||||
menu:Open()
|
||||
end
|
||||
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return !IsValid(item.entity)
|
||||
end
|
||||
}
|
||||
|
||||
-- Функция просмотра своего документа
|
||||
ITEM.functions.View = {
|
||||
name = "Просмотреть",
|
||||
tip = "Посмотреть содержимое военного билета",
|
||||
icon = "icon16/page_white_text.png",
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
|
||||
if SERVER then
|
||||
local data = {
|
||||
name = item:GetData("name", "Неизвестно"),
|
||||
faction = item:GetData("faction", "Неизвестно"),
|
||||
subdivision = item:GetData("subdivision", "Неизвестно"),
|
||||
specialization = item:GetData("specialization", "Неизвестно"),
|
||||
rank = item:GetData("rank", "Неизвестно")
|
||||
}
|
||||
|
||||
net.Start("ixDisguise_ViewStolenID")
|
||||
net.WriteTable(data)
|
||||
net.Send(client)
|
||||
end
|
||||
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return !IsValid(item.entity)
|
||||
end
|
||||
}
|
||||
|
||||
-- Кастомное отображение в инвентаре
|
||||
if CLIENT then
|
||||
function ITEM:PaintOver(item, w, h)
|
||||
local victimName = self:GetData("name")
|
||||
if victimName then
|
||||
draw.SimpleText(victimName, "DermaDefault", w/2, h - 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,101 @@
|
||||
ITEM.name = "Украденная форма"
|
||||
ITEM.description = "Военная форма, снятая с убитого противника. Позволяет замаскироваться под врага."
|
||||
ITEM.model = "models/props_c17/suitcase001a.mdl"
|
||||
ITEM.width = 2
|
||||
ITEM.height = 2
|
||||
ITEM.category = "Маскировка"
|
||||
ITEM.noDrop = false
|
||||
|
||||
-- Функция надевания формы
|
||||
ITEM.functions.Wear = {
|
||||
name = "Надеть",
|
||||
tip = "Надеть украденную форму для маскировки",
|
||||
icon = "icon16/user_suit.png",
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if SERVER then
|
||||
-- Проверяем кулдаун
|
||||
local plugin = ix.plugin.Get("disguise")
|
||||
local canUse, msg = plugin:CanUseDisguise(client)
|
||||
|
||||
if not canUse then
|
||||
client:Notify(msg)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Проверяем, не в маскировке ли уже
|
||||
if character:GetData("disguised") then
|
||||
client:Notify("Вы уже в маскировке!")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Получаем украденную модель
|
||||
local stolenModel = item:GetData("stolenModel")
|
||||
if not stolenModel then
|
||||
client:Notify("Форма повреждена!")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Сохраняем оригинальную модель и надеваем украденную
|
||||
local originalModel = client:GetModel()
|
||||
character:SetData("originalModel", originalModel)
|
||||
character:SetData("disguiseModel", stolenModel)
|
||||
character:SetData("disguised", true)
|
||||
|
||||
client:SetModel(stolenModel)
|
||||
|
||||
local victimName = item:GetData("victimName", "неизвестного")
|
||||
client:Notify("Вы надели форму " .. victimName .. ". Будьте осторожны!")
|
||||
|
||||
-- Удаляем предмет после использования
|
||||
item:Remove()
|
||||
end
|
||||
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return !IsValid(item.entity)
|
||||
end
|
||||
}
|
||||
|
||||
-- Функция снятия формы
|
||||
ITEM.functions.Remove = {
|
||||
name = "Снять",
|
||||
tip = "Снять маскировку",
|
||||
icon = "icon16/user_delete.png",
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if SERVER then
|
||||
if not character:GetData("disguised") then
|
||||
client:Notify("Вы не в маскировке!")
|
||||
return false
|
||||
end
|
||||
|
||||
local plugin = ix.plugin.Get("disguise")
|
||||
if plugin then
|
||||
plugin:RemoveDisguise(client)
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
local client = item.player
|
||||
local character = client and client:GetCharacter()
|
||||
return !IsValid(item.entity) and character and character:GetData("disguised")
|
||||
end
|
||||
}
|
||||
|
||||
-- Кастомное отображение в инвентаре
|
||||
if CLIENT then
|
||||
function ITEM:PaintOver(item, w, h)
|
||||
local victimName = self:GetData("victimName")
|
||||
if victimName then
|
||||
draw.SimpleText("Форма: " .. victimName, "DermaDefault", w/2, h - 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
PLUGIN.name = "Disguise System"
|
||||
PLUGIN.author = "Your Name"
|
||||
PLUGIN.description = "Система маскировки с украденной формой и документами"
|
||||
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
|
||||
if SERVER then
|
||||
util.AddNetworkString("ixDisguise_ViewStolenID")
|
||||
util.AddNetworkString("ixDisguise_ShowStolenID")
|
||||
end
|
||||
|
||||
-- Конфигурация
|
||||
PLUGIN.dropChance = 0.3 -- 30% шанс выпадения предметов
|
||||
PLUGIN.disguiseCooldown = 600 -- 10 минут в секундах
|
||||
@@ -0,0 +1,99 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
-- Таблица для хранения кулдаунов
|
||||
PLUGIN.disguiseCooldowns = PLUGIN.disguiseCooldowns or {}
|
||||
|
||||
-- Снятие маскировки
|
||||
function PLUGIN:RemoveDisguise(client)
|
||||
if not IsValid(client) then return end
|
||||
|
||||
local character = client:GetCharacter()
|
||||
if not character then return end
|
||||
|
||||
-- Возвращаем оригинальную модель
|
||||
local originalModel = character:GetModel()
|
||||
client:SetModel(originalModel)
|
||||
|
||||
-- Удаляем флаг маскировки
|
||||
character:SetData("disguised", false)
|
||||
character:SetData("disguiseModel", nil)
|
||||
|
||||
-- Устанавливаем кулдаун
|
||||
self.disguiseCooldowns[client:SteamID64()] = CurTime() + self.disguiseCooldown
|
||||
|
||||
client:Notify("Маскировка снята! Вы не сможете переодеться в течение 10 минут.")
|
||||
end
|
||||
|
||||
-- Проверка кулдауна
|
||||
function PLUGIN:CanUseDisguise(client)
|
||||
local steamID = client:SteamID64()
|
||||
local cooldown = self.disguiseCooldowns[steamID]
|
||||
|
||||
if cooldown and CurTime() < cooldown then
|
||||
local remaining = math.ceil(cooldown - CurTime())
|
||||
local minutes = math.floor(remaining / 60)
|
||||
local seconds = remaining % 60
|
||||
return false, string.format("Переодеться можно через %d:%02d", minutes, seconds)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Хук на получение урона
|
||||
function PLUGIN:EntityTakeDamage(target, dmg)
|
||||
if not IsValid(target) or not target:IsPlayer() then return end
|
||||
|
||||
local character = target:GetCharacter()
|
||||
if not character then return end
|
||||
|
||||
-- Проверяем, в маскировке ли игрок
|
||||
if character:GetData("disguised") then
|
||||
self:RemoveDisguise(target)
|
||||
end
|
||||
end
|
||||
|
||||
-- Хук на взятие оружия в руки
|
||||
function PLUGIN:PlayerSwitchWeapon(client, oldWeapon, newWeapon)
|
||||
if not IsValid(client) or not IsValid(newWeapon) then return end
|
||||
|
||||
local character = client:GetCharacter()
|
||||
if not character then return end
|
||||
|
||||
-- Проверяем, в маскировке ли игрок
|
||||
if character:GetData("disguised") then
|
||||
self:RemoveDisguise(client)
|
||||
end
|
||||
end
|
||||
|
||||
-- Отправка данных украденного военного билета
|
||||
net.Receive("ixDisguise_ShowStolenID", function(len, client)
|
||||
local targetID = net.ReadUInt(16)
|
||||
local itemID = net.ReadUInt(32)
|
||||
|
||||
local target = Player(targetID)
|
||||
if not IsValid(target) or target:GetPos():Distance(client:GetPos()) > 200 then
|
||||
client:Notify("Игрок слишком далеко!")
|
||||
return
|
||||
end
|
||||
|
||||
local item = ix.item.instances[itemID]
|
||||
if not item or item.player ~= client then
|
||||
return
|
||||
end
|
||||
|
||||
-- Отправляем данные документа целевому игроку
|
||||
local data = {
|
||||
name = item:GetData("name", "Неизвестно"),
|
||||
faction = item:GetData("faction", "Неизвестно"),
|
||||
subdivision = item:GetData("subdivision", "Неизвестно"),
|
||||
specialization = item:GetData("specialization", "Неизвестно"),
|
||||
rank = item:GetData("rank", "Неизвестно")
|
||||
}
|
||||
|
||||
net.Start("ixDisguise_ViewStolenID")
|
||||
net.WriteTable(data)
|
||||
net.Send(target)
|
||||
|
||||
client:Notify("Вы предъявили военный билет игроку " .. target:Name())
|
||||
target:Notify(client:Name() .. " предъявил вам военный билет")
|
||||
end)
|
||||
@@ -0,0 +1,3 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
-- Клиентские опции (если нужны дополнительные настройки на клиенте)
|
||||
@@ -0,0 +1,6 @@
|
||||
PLUGIN.name = "Dynamic Height"
|
||||
PLUGIN.author = "RefoselTeamWork"
|
||||
PLUGIN.description = "Автоматическая настройка высоты камеры игрока в зависимости от модели персонажа."
|
||||
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
@@ -0,0 +1,113 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
-- Конфигурационные переменные
|
||||
ix.config.Add("dynamicHeightEnabled", true, "Включить динамическую высоту камеры.", nil, {
|
||||
category = "Динамическая высота"
|
||||
})
|
||||
|
||||
ix.config.Add("dynamicHeightMaxManual", false, "Использовать ручную максимальную высоту.", nil, {
|
||||
category = "Динамическая высота"
|
||||
})
|
||||
|
||||
ix.config.Add("dynamicHeightMinManual", false, "Использовать ручную минимальную высоту.", nil, {
|
||||
category = "Динамическая высота"
|
||||
})
|
||||
|
||||
ix.config.Add("dynamicHeightMin", 16, "Минимальная высота камеры (присед).", nil, {
|
||||
data = {min = 5, max = 180},
|
||||
category = "Динамическая высота"
|
||||
})
|
||||
|
||||
ix.config.Add("dynamicHeightMax", 64, "Максимальная высота камеры (стоя).", nil, {
|
||||
data = {min = 5, max = 180},
|
||||
category = "Динамическая высота"
|
||||
})
|
||||
|
||||
local function UpdateView(ply)
|
||||
if not ix.config.Get("dynamicHeightEnabled", true) then
|
||||
if ply.ixDynamicHeightChanged then
|
||||
ply:SetViewOffset(Vector(0, 0, 64))
|
||||
ply:SetViewOffsetDucked(Vector(0, 0, 28))
|
||||
ply.ixDynamicHeightChanged = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Определяем высоту модели
|
||||
local height_max = 64
|
||||
local height_min = 16
|
||||
|
||||
-- Создаем временную сущность для измерения высоты стоя
|
||||
local entity = ents.Create("base_anim")
|
||||
entity:SetModel(ply:GetModel())
|
||||
entity:ResetSequence(entity:LookupSequence("idle_all_01"))
|
||||
local bone = entity:LookupBone("ValveBiped.Bip01_Neck1")
|
||||
if bone then
|
||||
height_max = entity:GetBonePosition(bone).z + 5
|
||||
end
|
||||
|
||||
-- Создаем временную сущность для измерения высоты приседа
|
||||
local entity2 = ents.Create("base_anim")
|
||||
entity2:SetModel(ply:GetModel())
|
||||
entity2:ResetSequence(entity2:LookupSequence("cidle_all"))
|
||||
local bone2 = entity2:LookupBone("ValveBiped.Bip01_Neck1")
|
||||
if bone2 then
|
||||
height_min = entity2:GetBonePosition(bone2).z + 5
|
||||
end
|
||||
|
||||
-- Удаляем временные сущности
|
||||
entity:Remove()
|
||||
entity2:Remove()
|
||||
|
||||
-- Применяем настройки высоты
|
||||
local min = ix.config.Get("dynamicHeightMin", 16)
|
||||
local max = ix.config.Get("dynamicHeightMax", 64)
|
||||
|
||||
if ix.config.Get("dynamicHeightMinManual", false) then
|
||||
ply:SetViewOffsetDucked(Vector(0, 0, min))
|
||||
else
|
||||
ply:SetViewOffsetDucked(Vector(0, 0, height_min))
|
||||
end
|
||||
|
||||
if ix.config.Get("dynamicHeightMaxManual", false) then
|
||||
ply:SetViewOffset(Vector(0, 0, max))
|
||||
else
|
||||
ply:SetViewOffset(Vector(0, 0, height_max))
|
||||
end
|
||||
|
||||
ply.ixDynamicHeightChanged = true
|
||||
end
|
||||
|
||||
local function UpdateTrueModel(ply)
|
||||
if ply:GetNWString("ixDynamicHeight:TrueModel") ~= ply:GetModel() then
|
||||
ply:SetNWString("ixDynamicHeight:TrueModel", ply:GetModel())
|
||||
UpdateView(ply)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerSpawn(ply)
|
||||
UpdateTrueModel(ply)
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerTick(ply)
|
||||
UpdateTrueModel(ply)
|
||||
end
|
||||
|
||||
-- Обновление при изменении конфигов
|
||||
local function OnConfigChanged(key, oldValue, newValue)
|
||||
if string.StartWith(key, "dynamicHeight") then
|
||||
for _, ply in pairs(player.GetAll()) do
|
||||
UpdateView(ply)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:OnConfigChanged(key, oldValue, newValue)
|
||||
if key == "dynamicHeightEnabled" or
|
||||
key == "dynamicHeightMin" or
|
||||
key == "dynamicHeightMax" or
|
||||
key == "dynamicHeightMinManual" or
|
||||
key == "dynamicHeightMaxManual" then
|
||||
OnConfigChanged(key, oldValue, newValue)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
net.Receive("AnnounceMessage", function()
|
||||
local msg = net.ReadString()
|
||||
chat.AddText(Color(255, 0, 0), msg)
|
||||
end)
|
||||
|
||||
local eventText = ""
|
||||
local eventTime = 0
|
||||
|
||||
net.Receive("EventMessage", function()
|
||||
eventText = net.ReadString()
|
||||
eventTime = CurTime() + 5 -- show for 5 seconds
|
||||
end)
|
||||
|
||||
function PLUGIN:HUDPaint()
|
||||
if CurTime() < eventTime then
|
||||
surface.SetFont("ixBigFont")
|
||||
local w, h = surface.GetTextSize(eventText)
|
||||
surface.SetTextColor(255, 255, 255)
|
||||
surface.SetTextPos(ScrW() / 2 - w / 2, 50)
|
||||
surface.DrawText(eventText)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
PLUGIN.name = "Event Announce"
|
||||
PLUGIN.author = "Scripty"
|
||||
PLUGIN.description = "/event /annonce"
|
||||
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
@@ -0,0 +1,21 @@
|
||||
util.AddNetworkString("EventMessage")
|
||||
util.AddNetworkString("AnnounceMessage")
|
||||
|
||||
function PLUGIN:PlayerSay(ply, text)
|
||||
local userGroup = ply:GetUserGroup() or ""
|
||||
if userGroup == "superadmin" or userGroup == "ivent" then
|
||||
if string.StartWith(text, "/event ") then
|
||||
local msg = string.sub(text, 8)
|
||||
net.Start("EventMessage")
|
||||
net.WriteString(msg)
|
||||
net.Broadcast()
|
||||
return ""
|
||||
elseif string.StartWith(text, "/annonce ") then
|
||||
local msg = string.sub(text, 10)
|
||||
net.Start("AnnounceMessage")
|
||||
net.WriteString(msg)
|
||||
net.Broadcast()
|
||||
return ""
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,115 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
local doomsday_active = false
|
||||
local doomsday_start_time = 0
|
||||
|
||||
net.Receive("doomsday_night_hud_update", function()
|
||||
doomsday_active = net.ReadBool()
|
||||
doomsday_start_time = net.ReadInt(32)
|
||||
end)
|
||||
|
||||
function PLUGIN:DoomsdayHUDPaint()
|
||||
local active = doomsday_active or GetGlobalBool("doomsday_night_active", false)
|
||||
local start_time = doomsday_start_time == 0 and GetGlobalInt("doomsday_night_start_time", 0) or doomsday_start_time
|
||||
local alive_count = GetGlobalInt("doomsday_night_alive_count", 0)
|
||||
|
||||
if (!active or start_time == 0) then return end
|
||||
|
||||
local elapsed = CurTime() - start_time
|
||||
local minutes = math.floor(elapsed / 60)
|
||||
local seconds = math.floor(elapsed % 60)
|
||||
|
||||
local x, y = ScrW() - 280, 20
|
||||
local w, h = 260, 120
|
||||
|
||||
surface.SetDrawColor(20, 20, 40, 200)
|
||||
surface.DrawRect(x, y, w, h)
|
||||
|
||||
local border_alpha = 150 + math.abs(math.sin(CurTime() * 2)) * 105
|
||||
surface.SetDrawColor(100, 0, 100, border_alpha)
|
||||
surface.DrawOutlinedRect(x, y, w, h, 3)
|
||||
|
||||
surface.SetFont("DermaDefaultBold")
|
||||
surface.SetTextColor(200, 100, 255, 255)
|
||||
local title = "🌙 СУДНАЯ НОЧЬ"
|
||||
local tw, th = surface.GetTextSize(title)
|
||||
surface.SetTextPos(x + (w - tw) / 2, y + 8)
|
||||
surface.DrawText(title)
|
||||
|
||||
surface.SetFont("DermaDefault")
|
||||
surface.SetTextColor(255, 255, 255, 255)
|
||||
local time_text = string.format("Время: %02d:%02d", minutes, seconds)
|
||||
tw, th = surface.GetTextSize(time_text)
|
||||
surface.SetTextPos(x + (w - tw) / 2, y + th + 15)
|
||||
surface.DrawText(time_text)
|
||||
|
||||
local alive_pulse = 200 + math.abs(math.sin(CurTime() * 3)) * 55
|
||||
surface.SetTextColor(255, 150, 150, alive_pulse)
|
||||
local alive_text = string.format("Выживших: %d", alive_count)
|
||||
tw, th = surface.GetTextSize(alive_text)
|
||||
surface.SetTextPos(x + (w - tw) / 2, y + th + 45)
|
||||
surface.DrawText(alive_text)
|
||||
|
||||
if (LocalPlayer():GetObserverMode() != OBS_MODE_NONE) then
|
||||
surface.SetTextColor(255, 100, 100, alive_pulse)
|
||||
local spec_text = "РЕЖИМ НАБЛЮДЕНИЯ"
|
||||
tw, th = surface.GetTextSize(spec_text)
|
||||
surface.SetTextPos(x + (w - tw) / 2, y + h - 25)
|
||||
surface.DrawText(spec_text)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:DoomsdayPlayerStats()
|
||||
if (!GetGlobalBool("doomsday_night_active", false)) then return end
|
||||
local ply = LocalPlayer()
|
||||
|
||||
local x, y, w, h = 20, 20, 220, 120
|
||||
surface.SetDrawColor(0, 0, 0, 150)
|
||||
surface.DrawRect(x, y, w, h)
|
||||
surface.SetDrawColor(100, 0, 100, 200)
|
||||
surface.DrawOutlinedRect(x, y, w, h, 2)
|
||||
|
||||
surface.SetFont("DermaDefaultBold")
|
||||
surface.SetTextColor(200, 200, 255, 255)
|
||||
surface.SetTextPos(x + 10, y + 10)
|
||||
surface.DrawText("📊 Ваша статистика:")
|
||||
|
||||
surface.SetFont("DermaDefault")
|
||||
surface.SetTextColor(255, 255, 255, 255)
|
||||
surface.SetTextPos(x + 10, y + 35)
|
||||
surface.DrawText(string.format("❤️ HP: %d", ply:Health()))
|
||||
surface.SetTextPos(x + 10, y + 55)
|
||||
surface.DrawText(string.format("🛡️ Armor: %d", ply:Armor()))
|
||||
|
||||
local weapon = ply:GetActiveWeapon()
|
||||
if (IsValid(weapon)) then
|
||||
local w_name = string.gsub(weapon:GetClass(), "tfa_ins2_", ""):upper()
|
||||
surface.SetTextPos(x + 10, y + 75)
|
||||
surface.DrawText("🔫 Оружие: " .. w_name)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:DoomsdayDeathEffect()
|
||||
if (!GetGlobalBool("doomsday_night_active", false)) then return end
|
||||
local ply = LocalPlayer()
|
||||
if (ply:Alive()) then return end
|
||||
|
||||
local alpha = 50 + math.abs(math.sin(CurTime() * 2)) * 30
|
||||
surface.SetDrawColor(150, 0, 0, alpha)
|
||||
surface.DrawRect(0, 0, ScrW(), ScrH())
|
||||
|
||||
surface.SetFont("DermaLarge")
|
||||
local title_pulse = 200 + math.abs(math.sin(CurTime() * 3)) * 55
|
||||
surface.SetTextColor(255, 50, 50, title_pulse)
|
||||
local text = "💀 ВЫБЫЛ 💀"
|
||||
local tw, th = surface.GetTextSize(text)
|
||||
surface.SetTextPos((ScrW() - tw) / 2, ScrH() / 2 - 100)
|
||||
surface.DrawText(text)
|
||||
end
|
||||
|
||||
function PLUGIN:HUDPaint()
|
||||
self:DrawEventHUD()
|
||||
self:DoomsdayHUDPaint()
|
||||
self:DoomsdayPlayerStats()
|
||||
self:DoomsdayDeathEffect()
|
||||
end
|
||||
@@ -0,0 +1,131 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
local event_active = false
|
||||
local event_start_time = 0
|
||||
local event_zone_active = false
|
||||
local event_zone_min = nil
|
||||
local event_zone_max = nil
|
||||
|
||||
-- Net Receivers
|
||||
net.Receive("events_system_notification", function()
|
||||
local message = net.ReadString()
|
||||
local lines = string.Split(message, "\n")
|
||||
for _, line in ipairs(lines) do
|
||||
if (string.Trim(line) != "") then
|
||||
chat.AddText(Color(255, 100, 100), " [EVENT] ", Color(255, 255, 255), line)
|
||||
end
|
||||
end
|
||||
surface.PlaySound("buttons/button15.wav")
|
||||
end)
|
||||
|
||||
net.Receive("events_system_hud_update", function()
|
||||
event_active = net.ReadBool()
|
||||
event_start_time = net.ReadInt(32)
|
||||
end)
|
||||
|
||||
net.Receive("events_system_zone_update", function()
|
||||
event_zone_active = net.ReadBool()
|
||||
if (event_zone_active) then
|
||||
event_zone_min = net.ReadVector()
|
||||
event_zone_max = net.ReadVector()
|
||||
else
|
||||
event_zone_min = nil
|
||||
event_zone_max = nil
|
||||
end
|
||||
end)
|
||||
|
||||
-- Fonts
|
||||
surface.CreateFont("EventHUD_Title", { font = "Roboto", size = 24, weight = 700, antialias = true, shadow = true })
|
||||
surface.CreateFont("EventHUD_Time", { font = "Roboto", size = 32, weight = 800, antialias = true, shadow = false })
|
||||
surface.CreateFont("EventHUD_Label", { font = "Roboto", size = 16, weight = 500, antialias = true })
|
||||
surface.CreateFont("EventHUD_Warning", { font = "Roboto", size = 14, weight = 600, antialias = true })
|
||||
|
||||
local function DrawGradientBox(x, y, w, h, color1, color2, vertical)
|
||||
local steps = 20
|
||||
local step_size = vertical and (h / steps) or (w / steps)
|
||||
for i = 0, steps - 1 do
|
||||
local t = i / steps
|
||||
local r = Lerp(t, color1.r, color2.r)
|
||||
local g = Lerp(t, color1.g, color2.g)
|
||||
local b = Lerp(t, color1.b, color2.b)
|
||||
local a = Lerp(t, color1.a, color2.a)
|
||||
surface.SetDrawColor(r, g, b, a)
|
||||
if (vertical) then
|
||||
surface.DrawRect(x, y + i * step_size, w, step_size + 1)
|
||||
else
|
||||
surface.DrawRect(x + i * step_size, y, step_size + 1, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:DrawEventHUD()
|
||||
local active = event_active or GetGlobalBool("events_system_active", false)
|
||||
local start_time = event_start_time == 0 and GetGlobalInt("events_system_start_time", 0) or event_start_time
|
||||
|
||||
if (!active or start_time == 0) then return end
|
||||
|
||||
local elapsed = CurTime() - start_time
|
||||
local minutes = math.floor(elapsed / 60)
|
||||
local seconds = math.floor(elapsed % 60)
|
||||
|
||||
local padding = 20
|
||||
local x = ScrW() - 320 - padding
|
||||
local y = padding
|
||||
local w, h = 320, 140
|
||||
|
||||
DrawGradientBox(x, y, w, h, Color(15, 15, 20, 220), Color(25, 25, 35, 200), true)
|
||||
|
||||
local pulse = math.abs(math.sin(CurTime() * 2))
|
||||
DrawGradientBox(x, y, w, 4, Color(255, 50, 80, 180 + pulse * 75), Color(255, 100, 50, 180 + pulse * 75), false)
|
||||
|
||||
surface.SetDrawColor(255, 80, 100, 100 + pulse * 80)
|
||||
for i = 1, 2 do surface.DrawOutlinedRect(x - i, y - i, w + i * 2, h + i * 2, 1) end
|
||||
|
||||
local offsetY = y + 15
|
||||
surface.SetFont("EventHUD_Title")
|
||||
surface.SetTextColor(255, 80 + pulse * 50, 80 + pulse * 50, 255)
|
||||
local title = "⚔ БОЕВОЙ ИВЕНТ"
|
||||
local tw, th = surface.GetTextSize(title)
|
||||
surface.SetTextPos(x + (w - tw) / 2, offsetY)
|
||||
surface.DrawText(title)
|
||||
|
||||
offsetY = offsetY + th + 10
|
||||
surface.SetFont("EventHUD_Time")
|
||||
surface.SetTextColor(255, 255, 255, 255)
|
||||
local time_text = string.format("%02d:%02d", minutes, seconds)
|
||||
tw, th = surface.GetTextSize(time_text)
|
||||
surface.SetTextPos(x + (w - tw) / 2, offsetY)
|
||||
surface.DrawText(time_text)
|
||||
|
||||
offsetY = offsetY + th + 10
|
||||
surface.SetFont("EventHUD_Warning")
|
||||
surface.SetTextColor(255, 220, 100, 200 + pulse * 55)
|
||||
local warning = "⚠ При смерти выбываете"
|
||||
tw, th = surface.GetTextSize(warning)
|
||||
surface.SetTextPos(x + (w - tw) / 2, offsetY)
|
||||
surface.DrawText(warning)
|
||||
end
|
||||
|
||||
function PLUGIN:PostDrawTranslucentRenderables()
|
||||
if (!event_zone_active or !event_zone_min or !event_zone_max) then return end
|
||||
|
||||
local min, max = event_zone_min, event_zone_max
|
||||
local corners = {
|
||||
Vector(min.x, min.y, min.z), Vector(max.x, min.y, min.z), Vector(max.x, max.y, min.z), Vector(min.x, max.y, min.z),
|
||||
Vector(min.x, min.y, max.z), Vector(max.x, min.y, max.z), Vector(max.x, max.y, max.z), Vector(min.x, max.y, max.z)
|
||||
}
|
||||
|
||||
local color = Color(0, 255, 100, 150)
|
||||
render.DrawLine(corners[1], corners[2], color, true)
|
||||
render.DrawLine(corners[2], corners[3], color, true)
|
||||
render.DrawLine(corners[3], corners[4], color, true)
|
||||
render.DrawLine(corners[4], corners[1], color, true)
|
||||
render.DrawLine(corners[5], corners[6], color, true)
|
||||
render.DrawLine(corners[6], corners[7], color, true)
|
||||
render.DrawLine(corners[7], corners[8], color, true)
|
||||
render.DrawLine(corners[8], corners[5], color, true)
|
||||
render.DrawLine(corners[1], corners[5], color, true)
|
||||
render.DrawLine(corners[2], corners[6], color, true)
|
||||
render.DrawLine(corners[3], corners[7], color, true)
|
||||
render.DrawLine(corners[4], corners[8], color, true)
|
||||
end
|
||||
@@ -0,0 +1,212 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
PLUGIN.name = "Система Ивентов"
|
||||
PLUGIN.author = "Scripty & RefoselTeamWork"
|
||||
PLUGIN.description = "Продвинутая система ивентов с сужающейся зоной и кастомными спавнами."
|
||||
|
||||
ix.config.Add("eventAllowedRanks", "superadmin,curator,ivent", "Ранги, которым разрешено управление ивентами (через запятую).", nil, {
|
||||
category = "Events"
|
||||
})
|
||||
|
||||
-- Shared Config (ported from sh_config.lua)
|
||||
PLUGIN.Config = {
|
||||
sounds = {
|
||||
event_start = "ambient/alarms/klaxon1.wav",
|
||||
event_end = "ambient/levels/citadel/citadel_hit.wav",
|
||||
player_join = "buttons/button15.wav"
|
||||
}
|
||||
}
|
||||
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
ix.util.Include("sv_doomsday.lua")
|
||||
ix.util.Include("cl_plugin.lua")
|
||||
ix.util.Include("cl_doomsday.lua")
|
||||
|
||||
-- Commands Adaptation
|
||||
ix.command.Add("EventStart", {
|
||||
description = "Запустить стандартный боевой ивент.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
if (PLUGIN.active) then
|
||||
return "Ивент уже активен!"
|
||||
end
|
||||
PLUGIN:StartEvent(client)
|
||||
return "Запуск ивента..."
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventStop", {
|
||||
description = "Остановить текущий активный ивент.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
if (!PLUGIN.active) then
|
||||
return "Нет активного ивента для остановки!"
|
||||
end
|
||||
PLUGIN:EndEvent(client)
|
||||
return "Остановка ивента..."
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventOtkat", {
|
||||
description = "Отменить активный ивент с уведомлением.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
if (!PLUGIN.active) then
|
||||
return "Нет активного ивента для отмены!"
|
||||
end
|
||||
PLUGIN:EndEvent(client, true)
|
||||
return "Отмена ивента..."
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventJoin", {
|
||||
description = "Присоединиться к активному ивенту.",
|
||||
OnRun = function(self, client)
|
||||
if (!PLUGIN.active) then
|
||||
return "Нет активного ивента для входа."
|
||||
end
|
||||
|
||||
if (PLUGIN.participants[client:SteamID()]) then
|
||||
return "Вы уже участвуете!"
|
||||
end
|
||||
|
||||
PLUGIN:AddParticipant(client)
|
||||
|
||||
local participants_count = table.Count(PLUGIN.participants)
|
||||
PLUGIN:BroadcastMessage(string.format("%s присоединился к ивенту! Участников: %d", client:Nick(), participants_count))
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventAddAll", {
|
||||
description = "Добавить всех игроков онлайн в ивент.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
if (!PLUGIN.active) then
|
||||
return "Ивент не активен!"
|
||||
end
|
||||
PLUGIN:AddAllPlayers(client)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventSetZone1", {
|
||||
description = "Установить первую точку зоны ивента.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
PLUGIN:SetZonePoint1(client)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventSetZone2", {
|
||||
description = "Установить вторую точку зоны ивента.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
PLUGIN:SetZonePoint2(client)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventClearZone", {
|
||||
description = "Очистить зону ивента.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
PLUGIN:ClearZone(client)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventZoneStart", {
|
||||
description = "Начать сужение зоны ивента.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
PLUGIN:StartZoneShrinking(client)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventZoneStop", {
|
||||
description = "Остановить сужение зоны ивента.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
PLUGIN:StopZoneShrinking(client)
|
||||
end
|
||||
})
|
||||
|
||||
-- Temporary Spawns Commands
|
||||
ix.command.Add("EventSpawnAdd", {
|
||||
description = "Добавить временный спавн для фракции или spec_def.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.string, -- Faction Name
|
||||
bit.bor(ix.type.number, ix.type.optional) -- spec_def
|
||||
},
|
||||
OnRun = function(self, client, factionName, specDef)
|
||||
local faction = ix.faction.teams[factionName]
|
||||
if (!faction) then
|
||||
-- Try fuzzy match
|
||||
for _, v in pairs(ix.faction.indices) do
|
||||
if (ix.util.StringMatches(v.name, factionName) or ix.util.StringMatches(v.uniqueID, factionName)) then
|
||||
faction = v
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (!faction) then
|
||||
return "Некорректное название фракции!"
|
||||
end
|
||||
|
||||
PLUGIN:AddTempSpawn(faction.uniqueID, specDef, client:GetPos(), client:GetAngles())
|
||||
|
||||
local msg = "Временный спавн добавлен для фракции: " .. faction.name
|
||||
if (specDef) then
|
||||
msg = msg .. " (spec_def: " .. specDef .. ")"
|
||||
end
|
||||
return msg
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("EventSpawnRemove", {
|
||||
description = "Удалить временные спавны ивента в радиусе.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
bit.bor(ix.type.number, ix.type.optional) -- Radius
|
||||
},
|
||||
OnRun = function(self, client, radius)
|
||||
radius = radius or 120
|
||||
local count = PLUGIN:RemoveTempSpawns(client:GetPos(), radius)
|
||||
return "Удалено " .. count .. " временных спавнов."
|
||||
end
|
||||
})
|
||||
|
||||
-- Doomsday Commands
|
||||
ix.command.Add("DoomsdayStart", {
|
||||
description = "Запустить ивент Судная Ночь.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
if (DoomsdayNight.active) then
|
||||
return "Судная Ночь уже активна!"
|
||||
end
|
||||
DoomsdayNight:StartEvent(client)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("DoomsdayStop", {
|
||||
description = "Остановить ивент Судная Ночь.",
|
||||
privilege = "Manage Events",
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client)
|
||||
if (!DoomsdayNight.active) then
|
||||
return "Судная Ночь не активна!"
|
||||
end
|
||||
DoomsdayNight:EndEvent(client, true)
|
||||
end
|
||||
})
|
||||
@@ -0,0 +1,184 @@
|
||||
DoomsdayNight = DoomsdayNight or {}
|
||||
DoomsdayNight.active = false
|
||||
DoomsdayNight.start_time = 0
|
||||
DoomsdayNight.participants = {}
|
||||
DoomsdayNight.spectators = {}
|
||||
DoomsdayNight.spawn_positions = {}
|
||||
|
||||
DoomsdayNight.weapons = {
|
||||
"tfa_ins2_ak74", "tfa_ins2_m4a1", "tfa_ins2_ak12", "tfa_ins2_m16a4", "tfa_ins2_scar_h_ssr",
|
||||
"tfa_ins2_m40a1", "tfa_ins2_mosin", "tfa_ins2_svd", "tfa_ins2_m590", "tfa_ins2_toz",
|
||||
"tfa_ins2_glock_17", "tfa_ins2_m9", "tfa_ins2_makarov", "tfa_ins2_mp5", "tfa_ins2_ump45", "tfa_ins2_mp40"
|
||||
}
|
||||
|
||||
util.AddNetworkString("doomsday_night_notification")
|
||||
util.AddNetworkString("doomsday_night_hud_update")
|
||||
|
||||
function DoomsdayNight:Init()
|
||||
SetGlobalBool("doomsday_night_active", false)
|
||||
SetGlobalInt("doomsday_night_start_time", 0)
|
||||
SetGlobalInt("doomsday_night_alive_count", 0)
|
||||
self:GenerateSpawnPositions()
|
||||
end
|
||||
|
||||
function DoomsdayNight:GenerateSpawnPositions()
|
||||
local map = game.GetMap()
|
||||
local map_positions = {
|
||||
["gm_construct"] = {
|
||||
Vector(980, -1500, -79), Vector(-700, 1000, 200), Vector(1500, 800, 100), Vector(-1200, -800, 50)
|
||||
},
|
||||
["rp_valkyrie_forest_v1"] = {
|
||||
Vector(5000, 3000, 200), Vector(-5000, -3000, 150), Vector(8000, -2000, 100), Vector(-8000, 2000, 180)
|
||||
}
|
||||
}
|
||||
self.spawn_positions = map_positions[map] or { Vector(1000, 1000, 100), Vector(-1000, -1000, 100) }
|
||||
end
|
||||
|
||||
function DoomsdayNight:StartEvent(ply)
|
||||
local players = player.GetAll()
|
||||
if (#players < 2) then
|
||||
ply:Notify("Недостаточно игроков для начала Судной Ночи!")
|
||||
return
|
||||
end
|
||||
|
||||
self.active = true
|
||||
self.start_time = CurTime()
|
||||
self.participants = {}
|
||||
self.spectators = {}
|
||||
|
||||
SetGlobalBool("doomsday_night_active", true)
|
||||
SetGlobalInt("doomsday_night_start_time", CurTime())
|
||||
|
||||
for _, player in ipairs(players) do
|
||||
if (IsValid(player)) then self:PreparePlayer(player) end
|
||||
end
|
||||
|
||||
self:BroadcastMessage("🌙 СУДНАЯ НОЧЬ НАЧАЛАСЬ! 🌙\nСражайтесь до последнего выжившего!\nВыдано случайное оружие.")
|
||||
|
||||
timer.Simple(3, function() if (self.active) then self:TeleportPlayers() end end)
|
||||
self:UpdateHUD()
|
||||
end
|
||||
|
||||
function DoomsdayNight:PreparePlayer(ply)
|
||||
local steamid = ply:SteamID()
|
||||
self.participants[steamid] = {
|
||||
start_pos = ply:GetPos(),
|
||||
join_time = CurTime(),
|
||||
kills = 0,
|
||||
nick = ply:Nick(),
|
||||
alive = true
|
||||
}
|
||||
ply:StripWeapons()
|
||||
self:GiveRandomWeapons(ply)
|
||||
end
|
||||
|
||||
function DoomsdayNight:GiveRandomWeapons(ply)
|
||||
if (!IsValid(ply) or !ply:Alive()) then return end
|
||||
ply:Give("ix_hands")
|
||||
|
||||
local weapons_to_give = {}
|
||||
local used = {}
|
||||
for i = 1, math.random(2, 3) do
|
||||
local w = self.weapons[math.random(1, #self.weapons)]
|
||||
if (!used[w]) then
|
||||
used[w] = true
|
||||
table.insert(weapons_to_give, w)
|
||||
end
|
||||
end
|
||||
|
||||
for _, w_class in ipairs(weapons_to_give) do
|
||||
local weapon = ply:Give(w_class)
|
||||
if (IsValid(weapon)) then
|
||||
ply:GiveAmmo(500, weapon:GetPrimaryAmmoType(), true)
|
||||
end
|
||||
end
|
||||
|
||||
ply:Give("weapon_frag")
|
||||
ply:GiveAmmo(2, "Grenade", true)
|
||||
ply:SetHealth(100)
|
||||
ply:SetArmor(100)
|
||||
end
|
||||
|
||||
function DoomsdayNight:TeleportPlayers()
|
||||
local players = {}
|
||||
for steamid, data in pairs(self.participants) do
|
||||
local ply = player.GetBySteamID(steamid)
|
||||
if (IsValid(ply)) then table.insert(players, ply) end
|
||||
end
|
||||
|
||||
local positions = table.Copy(self.spawn_positions)
|
||||
for i, ply in ipairs(players) do
|
||||
local spawn_pos = positions[((i - 1) % #positions) + 1]
|
||||
ply:SetPos(spawn_pos)
|
||||
ply:SetEyeAngles(Angle(0, math.random(0, 360), 0))
|
||||
end
|
||||
|
||||
SetGlobalInt("doomsday_night_alive_count", #players)
|
||||
self:BroadcastMessage("⚡ Игроки телепортированы! К БОЮ!")
|
||||
end
|
||||
|
||||
function DoomsdayNight:EndEvent(ply, cancelled)
|
||||
if (!self.active) then return end
|
||||
self.active = false
|
||||
SetGlobalBool("doomsday_night_active", false)
|
||||
|
||||
for steamid, _ in pairs(self.spectators) do
|
||||
local spec_ply = player.GetBySteamID(steamid)
|
||||
if (IsValid(spec_ply)) then
|
||||
spec_ply:UnSpectate()
|
||||
spec_ply:Spawn()
|
||||
end
|
||||
end
|
||||
|
||||
self:BroadcastMessage(cancelled and "🛑 СУДНАЯ НОЧЬ ОТМЕНЕНА" or "🏆 СУДНАЯ НОЧЬ ЗАВЕРШЕНА")
|
||||
self.participants = {}
|
||||
self.spectators = {}
|
||||
self:UpdateHUD()
|
||||
end
|
||||
|
||||
function DoomsdayNight:OnPlayerKilled(victim, attacker)
|
||||
if (!self.active) then return end
|
||||
local data = self.participants[victim:SteamID()]
|
||||
if (!data) then return end
|
||||
|
||||
data.alive = false
|
||||
if (IsValid(attacker) and attacker:IsPlayer()) then
|
||||
local attacker_data = self.participants[attacker:SteamID()]
|
||||
if (attacker_data) then attacker_data.kills = attacker_data.kills + 1 end
|
||||
end
|
||||
|
||||
timer.Simple(2, function()
|
||||
if (IsValid(victim)) then
|
||||
self.spectators[victim:SteamID()] = true
|
||||
victim:Spectate(OBS_MODE_ROAMING)
|
||||
victim:Notify("👁️ Вы перешли в режим наблюдения до конца ивента.")
|
||||
end
|
||||
end)
|
||||
|
||||
local alive_count = 0
|
||||
for _, d in pairs(self.participants) do
|
||||
if (d.alive) then alive_count = alive_count + 1 end
|
||||
end
|
||||
SetGlobalInt("doomsday_night_alive_count", alive_count)
|
||||
|
||||
if (alive_count <= 1) then
|
||||
timer.Simple(3, function() if (self.active) then self:EndEvent() end end)
|
||||
end
|
||||
end
|
||||
|
||||
function DoomsdayNight:BroadcastMessage(msg)
|
||||
for _, ply in ipairs(player.GetAll()) do ply:ChatPrint(msg) end
|
||||
end
|
||||
|
||||
function DoomsdayNight:UpdateHUD()
|
||||
net.Start("doomsday_night_hud_update")
|
||||
net.WriteBool(self.active)
|
||||
net.WriteInt(self.start_time, 32)
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
-- Hook aliases for PLUGIN
|
||||
function PLUGIN:DoomsdayNight_Init() DoomsdayNight:Init() end
|
||||
function PLUGIN:DoomsdayNight_PlayerDeath(victim, inflictor, attacker) DoomsdayNight:OnPlayerKilled(victim, attacker) end
|
||||
|
||||
hook.Add("InitPostEntity", "DoomsdayNight_Init", function() DoomsdayNight:Init() end)
|
||||
@@ -0,0 +1,360 @@
|
||||
local PLUGIN = PLUGIN
|
||||
|
||||
PLUGIN.active = PLUGIN.active or false
|
||||
PLUGIN.start_time = PLUGIN.start_time or 0
|
||||
PLUGIN.participants = PLUGIN.participants or {}
|
||||
PLUGIN.eliminated_players = PLUGIN.eliminated_players or {}
|
||||
PLUGIN.initiator = PLUGIN.initiator or nil
|
||||
PLUGIN.tempSpawns = PLUGIN.tempSpawns or {} -- [factionUniqueID] = { [specDef or "default"] = { {pos, ang}, ... } }
|
||||
|
||||
-- Zone Data
|
||||
PLUGIN.zone = PLUGIN.zone or {
|
||||
pos1 = nil,
|
||||
pos2 = nil,
|
||||
min = nil,
|
||||
max = nil,
|
||||
shrinking = false,
|
||||
center = nil,
|
||||
shrink_speed = 1,
|
||||
min_size = 500
|
||||
}
|
||||
|
||||
-- Networking
|
||||
util.AddNetworkString("events_system_notification")
|
||||
util.AddNetworkString("events_system_hud_update")
|
||||
util.AddNetworkString("events_system_zone_update")
|
||||
|
||||
-- Initialization
|
||||
function PLUGIN:InitPostEntity()
|
||||
SetGlobalBool("events_system_active", false)
|
||||
SetGlobalInt("events_system_start_time", 0)
|
||||
|
||||
self.tempSpawns = self:GetData() or {}
|
||||
end
|
||||
|
||||
function PLUGIN:SaveTempSpawns()
|
||||
self:SetData(self.tempSpawns)
|
||||
end
|
||||
|
||||
-- Permissions Check (using ix.config)
|
||||
function PLUGIN:HasPermission(ply)
|
||||
if (!IsValid(ply)) then return false end
|
||||
if (ply:IsSuperAdmin()) then return true end
|
||||
|
||||
local allowed = string.Split(ix.config.Get("eventAllowedRanks", ""), ",")
|
||||
local userGroup = ply:GetUserGroup()
|
||||
|
||||
for _, rank in ipairs(allowed) do
|
||||
if (string.lower(userGroup) == string.lower(string.Trim(rank))) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Zone Logic
|
||||
function PLUGIN:SetZonePoint1(ply)
|
||||
self.zone.pos1 = ply:GetPos()
|
||||
self:SendMessage(ply, "✅ Точка зоны 1 установлена: " .. tostring(self.zone.pos1))
|
||||
if (self.zone.pos2) then self:CalculateZoneBounds() end
|
||||
end
|
||||
|
||||
function PLUGIN:SetZonePoint2(ply)
|
||||
self.zone.pos2 = ply:GetPos()
|
||||
self:SendMessage(ply, "✅ Точка зоны 2 установлена: " .. tostring(self.zone.pos2))
|
||||
if (self.zone.pos1) then self:CalculateZoneBounds() end
|
||||
end
|
||||
|
||||
function PLUGIN:CalculateZoneBounds()
|
||||
if (!self.zone.pos1 or !self.zone.pos2) then return end
|
||||
|
||||
self.zone.min = Vector(
|
||||
math.min(self.zone.pos1.x, self.zone.pos2.x),
|
||||
math.min(self.zone.pos1.y, self.zone.pos2.y),
|
||||
math.min(self.zone.pos1.z, self.zone.pos2.z)
|
||||
)
|
||||
|
||||
self.zone.max = Vector(
|
||||
math.max(self.zone.pos1.x, self.zone.pos2.x),
|
||||
math.max(self.zone.pos1.y, self.zone.pos2.y),
|
||||
math.max(self.zone.pos1.z, self.zone.pos2.z)
|
||||
)
|
||||
|
||||
self:BroadcastZone()
|
||||
local size = self.zone.max - self.zone.min
|
||||
self:BroadcastMessage(string.format("🎯 Зона ивента установлена! Размер: %.0f x %.0f x %.0f", size.x, size.y, size.z))
|
||||
end
|
||||
|
||||
function PLUGIN:BroadcastZone()
|
||||
if (!self.zone.min or !self.zone.max) then return end
|
||||
net.Start("events_system_zone_update")
|
||||
net.WriteBool(true)
|
||||
net.WriteVector(self.zone.min)
|
||||
net.WriteVector(self.zone.max)
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
function PLUGIN:ClearZone(ply)
|
||||
self.zone.pos1 = nil
|
||||
self.zone.pos2 = nil
|
||||
self.zone.min = nil
|
||||
self.zone.max = nil
|
||||
self.zone.shrinking = false
|
||||
|
||||
if (timer.Exists("EventsSystem_ZoneShrink")) then
|
||||
timer.Remove("EventsSystem_ZoneShrink")
|
||||
end
|
||||
|
||||
net.Start("events_system_zone_update")
|
||||
net.WriteBool(false)
|
||||
net.Broadcast()
|
||||
|
||||
if (IsValid(ply)) then
|
||||
self:SendMessage(ply, "🗑️ Зона ивента очищена.")
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:StartZoneShrinking(ply)
|
||||
if (!self.zone.min or !self.zone.max) then
|
||||
if (IsValid(ply)) then self:SendMessage(ply, "❌ Сначала установите зону!") end
|
||||
return
|
||||
end
|
||||
|
||||
if (self.zone.shrinking) then
|
||||
if (IsValid(ply)) then self:SendMessage(ply, "⚠️ Зона уже сужается!") end
|
||||
return
|
||||
end
|
||||
|
||||
self.zone.center = (self.zone.min + self.zone.max) / 2
|
||||
self.zone.shrinking = true
|
||||
|
||||
timer.Create("EventsSystem_ZoneShrink", 0.1, 0, function()
|
||||
self:ShrinkZone()
|
||||
end)
|
||||
|
||||
self:BroadcastMessage("🔴 ВНИМАНИЕ! Зона начала сужаться к центру!")
|
||||
for _, player in ipairs(player.GetAll()) do
|
||||
player:EmitSound("ambient/alarms/warningbell1.wav", 75, 100)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:StopZoneShrinking(ply)
|
||||
if (!self.zone.shrinking) then return end
|
||||
self.zone.shrinking = false
|
||||
timer.Remove("EventsSystem_ZoneShrink")
|
||||
|
||||
local size = self.zone.max - self.zone.min
|
||||
self:BroadcastMessage(string.format("⏸️ Сужение зоны остановлено! Текущий размер: %.0f x %.0f", size.x, size.y))
|
||||
end
|
||||
|
||||
function PLUGIN:ShrinkZone()
|
||||
if (!self.zone.shrinking or !self.zone.min or !self.zone.max or !self.zone.center) then return end
|
||||
|
||||
local shrink_amount = self.zone.shrink_speed * 0.1
|
||||
local new_min = Vector(self.zone.min.x + shrink_amount, self.zone.min.y + shrink_amount, self.zone.min.z)
|
||||
local new_max = Vector(self.zone.max.x - shrink_amount, self.zone.max.y - shrink_amount, self.zone.max.z)
|
||||
|
||||
if (new_max.x - new_min.x <= self.zone.min_size or new_max.y - new_min.y <= self.zone.min_size) then
|
||||
self:StopZoneShrinking(nil)
|
||||
self:BroadcastMessage("❗ Зона достигла минимального размера!")
|
||||
return
|
||||
end
|
||||
|
||||
self.zone.min = new_min
|
||||
self.zone.max = new_max
|
||||
self:BroadcastZone()
|
||||
end
|
||||
|
||||
function PLUGIN:IsInZone(pos)
|
||||
if (!self.zone.min or !self.zone.max) then return true end
|
||||
return pos.x >= self.zone.min.x and pos.x <= self.zone.max.x and
|
||||
pos.y >= self.zone.min.y and pos.y <= self.zone.max.y and
|
||||
pos.z >= self.zone.min.z and pos.z <= self.zone.max.z
|
||||
end
|
||||
|
||||
function PLUGIN:CheckZoneBoundaries()
|
||||
if (!self.active) then return end
|
||||
for steamid, data in pairs(self.participants) do
|
||||
local ply = player.GetBySteamID(steamid)
|
||||
if (IsValid(ply) and ply:Alive()) then
|
||||
if (!self:IsInZone(ply:GetPos())) then
|
||||
ply:TakeDamage(10, ply, ply)
|
||||
ply:Notify("⚠️ ВНИМАНИЕ! Вы вне зоны ивента! Немедленно вернитесь!")
|
||||
ply:EmitSound("buttons/button10.wav", 75, 100)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Event Control
|
||||
function PLUGIN:StartEvent(ply)
|
||||
self.active = true
|
||||
self.start_time = CurTime()
|
||||
self.participants = {}
|
||||
self.eliminated_players = {}
|
||||
self.initiator = ply
|
||||
|
||||
SetGlobalBool("events_system_active", true)
|
||||
SetGlobalInt("events_system_start_time", CurTime())
|
||||
|
||||
local initiator_name = IsValid(ply) and ply:Nick() or "Администратор"
|
||||
local start_message = "⚔️ БОЕВОЙ ИВЕНТ НАЧАЛСЯ! ⚔️\nОрганизатор: " .. initiator_name .. "\nКак участвовать: Используйте /EventJoin\nЦель: Выжить и победить!"
|
||||
self:BroadcastMessage(start_message)
|
||||
|
||||
for _, player in ipairs(player.GetAll()) do
|
||||
player:EmitSound(self.Config.sounds.event_start, 75, 100, 0.8)
|
||||
end
|
||||
|
||||
self:UpdateHUD()
|
||||
timer.Create("EventsSystem_ZoneCheck", 1, 0, function() self:CheckZoneBoundaries() end)
|
||||
end
|
||||
|
||||
function PLUGIN:EndEvent(ply, cancelled)
|
||||
if (!self.active) then return end
|
||||
|
||||
local stats_lines = {
|
||||
"╔═══════════════════════════════════════╗",
|
||||
"║ 📊 ФИНАЛЬНАЯ СТАТИСТИКА 📊 ║",
|
||||
"╚═══════════════════════════════════════╝",
|
||||
string.format("👥 Всего участников: %d", table.Count(self.participants) + table.Count(self.eliminated_players))
|
||||
}
|
||||
|
||||
self.active = false
|
||||
SetGlobalBool("events_system_active", false)
|
||||
SetGlobalInt("events_system_start_time", 0)
|
||||
|
||||
local end_message = cancelled and "🛑 ИВЕНТ ОТМЕНЕН АДМИНИСТРАТОРОМ" or "🏆 ИВЕНТ ЗАВЕРШЕН 🏆"
|
||||
self:BroadcastMessage(end_message)
|
||||
for _, line in ipairs(stats_lines) do self:BroadcastMessage(line) end
|
||||
|
||||
for _, player in ipairs(player.GetAll()) do
|
||||
player:EmitSound(self.Config.sounds.event_end, 75, 100, 0.6)
|
||||
end
|
||||
|
||||
self.participants = {}
|
||||
self.eliminated_players = {}
|
||||
timer.Remove("EventsSystem_ZoneCheck")
|
||||
self:UpdateHUD()
|
||||
end
|
||||
|
||||
function PLUGIN:AddParticipant(ply)
|
||||
if (!self.active or !IsValid(ply)) then return end
|
||||
self.participants[ply:SteamID()] = {
|
||||
join_time = CurTime(),
|
||||
kills = 0,
|
||||
nick = ply:Nick()
|
||||
}
|
||||
ply:Notify("✅ ВЫ ПРИСОЕДИНИЛИСЬ К ИВЕНТУ!")
|
||||
ply:EmitSound(self.Config.sounds.player_join, 50, 100, 0.5)
|
||||
end
|
||||
|
||||
function PLUGIN:AddAllPlayers(admin_ply)
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
self:AddParticipant(ply)
|
||||
end
|
||||
self:BroadcastMessage("📢 Все игроки добавлены в ивент!")
|
||||
end
|
||||
|
||||
-- Hooks
|
||||
function PLUGIN:PlayerDeath(victim, inflictor, attacker)
|
||||
if (!self.active) then return end
|
||||
local steamid = victim:SteamID()
|
||||
local data = self.participants[steamid]
|
||||
if (!data) then return end
|
||||
|
||||
if (IsValid(attacker) and attacker:IsPlayer()) then
|
||||
local attacker_data = self.participants[attacker:SteamID()]
|
||||
if (attacker_data) then attacker_data.kills = attacker_data.kills + 1 end
|
||||
end
|
||||
|
||||
self.eliminated_players[steamid] = data
|
||||
self.participants[steamid] = nil
|
||||
|
||||
victim:Notify("💀 Вы выбыли из ивента!")
|
||||
self:BroadcastMessage(string.format("💀 %s выбыл! Убийств: %d", victim:Nick(), data.kills))
|
||||
|
||||
timer.Simple(0.5, function() self:CheckEventEnd() end)
|
||||
end
|
||||
|
||||
function PLUGIN:CheckEventEnd()
|
||||
if (!self.active) then return end
|
||||
if (table.Count(self.participants) <= 1) then
|
||||
self:EndEvent()
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:PlayerDisconnected(ply)
|
||||
if (!self.active) then return end
|
||||
self.participants[ply:SteamID()] = nil
|
||||
end
|
||||
|
||||
-- Messaging
|
||||
function PLUGIN:SendMessage(ply, message)
|
||||
if (!IsValid(ply)) then return end
|
||||
net.Start("events_system_notification")
|
||||
net.WriteString(message)
|
||||
net.Send(ply)
|
||||
end
|
||||
|
||||
function PLUGIN:BroadcastMessage(message)
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
ply:ChatPrint(message)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:UpdateHUD()
|
||||
net.Start("events_system_hud_update")
|
||||
net.WriteBool(self.active)
|
||||
net.WriteInt(self.start_time, 32)
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
-- Temporary Spawns Implementation
|
||||
function PLUGIN:AddTempSpawn(factionID, specDef, pos, ang)
|
||||
specDef = specDef or "default"
|
||||
self.tempSpawns[factionID] = self.tempSpawns[factionID] or {}
|
||||
self.tempSpawns[factionID][specDef] = self.tempSpawns[factionID][specDef] or {}
|
||||
|
||||
table.insert(self.tempSpawns[factionID][specDef], {pos = pos, ang = ang})
|
||||
self:SaveTempSpawns()
|
||||
end
|
||||
|
||||
function PLUGIN:RemoveTempSpawns(pos, radius)
|
||||
local count = 0
|
||||
for factionID, specs in pairs(self.tempSpawns) do
|
||||
for specDef, points in pairs(specs) do
|
||||
for k, v in pairs(points) do
|
||||
if (v.pos:Distance(pos) <= radius) then
|
||||
points[k] = nil
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (count > 0) then self:SaveTempSpawns() end
|
||||
return count
|
||||
end
|
||||
|
||||
function PLUGIN:PostPlayerLoadout(client)
|
||||
if (!self.active) then return end
|
||||
|
||||
local char = client:GetCharacter()
|
||||
if (!char) then return end
|
||||
|
||||
local faction = ix.faction.indices[client:Team()]
|
||||
if (!faction) then return end
|
||||
|
||||
local factionID = faction.uniqueID
|
||||
local podr = char:GetPodr()
|
||||
local factionTable = ix.faction.Get(client:Team())
|
||||
local specDef = (factionTable and factionTable.Podr and factionTable.Podr[podr]) and factionTable.Podr[podr].spec_def or "default"
|
||||
|
||||
local points = (self.tempSpawns[factionID] and self.tempSpawns[factionID][specDef]) or (self.tempSpawns[factionID] and self.tempSpawns[factionID]["default"])
|
||||
|
||||
if (points and !table.IsEmpty(points)) then
|
||||
local point = table.Random(points)
|
||||
client:SetPos(point.pos)
|
||||
client:SetEyeAngles(point.ang)
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user