add sborka

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

View File

@@ -0,0 +1,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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
if SERVER then
game.SetAmmoMax("rpg_round", 1)
game.SetAmmoMax("PanzerFaust3 Rocket", 1)
end

View File

@@ -0,0 +1,2 @@
DeriveGamemode("helix")

View 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

View File

@@ -0,0 +1,8 @@
"exp_surv"
{
"base" "helix"
"title" "FT Team VNU"
"author" "nebulous"
"menusystem" "1"
}

View 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)

View File

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

View 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

View 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)

View 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

View File

@@ -0,0 +1 @@
local PLUGIN = PLUGIN

View File

@@ -0,0 +1,5 @@
include("shared.lua")
function ENT:Draw()
self:DrawModel()
end

View File

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

View File

@@ -0,0 +1,6 @@
ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Airdrop"
ENT.Category = "Helix"
ENT.Spawnable = false
ENT.AdminOnly = true

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

View 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

View File

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

View File

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

View 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)

View File

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

View File

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

View File

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

View 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 = {}
}

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
include( "shared.lua" )
function ENT:Draw()
self.BaseClass.Draw(self)
self.Entity:DrawModel()
end

View File

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

View File

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

View File

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

View 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

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

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View 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

View 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 минут в секундах

View File

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

View File

@@ -0,0 +1,3 @@
local PLUGIN = PLUGIN
-- Клиентские опции (если нужны дополнительные настройки на клиенте)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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