Система дверей хранит владение, но не отпирает и не запирает двери сама: это делает оружие entities/weapons/keys.lua через ENTITY:CanBeOpenedBy и ent:Fire("lock"/"unlock").
1. Логика замка
ЛКМ запирает, ПКМ отпирает, обе кнопки вызывают один и тот же метод ToggleDoor:
function SWEP:PrimaryAttack()
self:SetNextPrimaryFire(CurTime() + 1)
self:ToggleDoor("lock", false)
end
function SWEP:SecondaryAttack()
self:SetNextSecondaryFire(CurTime() + 1)
self:ToggleDoor("unlock", true)
end
function SWEP:ToggleDoor(state, hard)
local ply = self:GetOwner()
local tr = ply:GetEyeTrace()
local door = tr.Entity
if not IsValid(door) or not door:IsDoor() then return end
if ply:GetShootPos():Distance(tr.HitPos) > self.DoorRange then return end
if not door:CanBeOpenedBy(ply) then
self:Knock(door, hard)
return
end
if CLIENT then return end
door:Fire(state)
local partner = door:GetPartnerDoor()
if IsValid(partner) then partner:Fire(state) end
self:EmitSound("doors/door_latch3.wav")
end
CanBeOpenedBy уже учитывает владельца, совладельцев, команду и профессию: оружие не дублирует эту логику. Двойные двери запираются вместе через ENTITY:GetPartnerDoor.
2. Стук по чужой двери
Если доступа нет, вместо Fire игрок «стучит»: проигрывается анимация руки (жёстче на ПКМ) и звук удара по двери с обеих сторон сети (клиент играет анимацию сразу, звук ставится на сервере с задержкой):
function SWEP:Knock(door, hard)
if not IsFirstTimePredicted() then return end
self.Idle = CurTime() + (self:PlayAnim(hard and "hard" or "soft") or 1)
if CLIENT then return end
local ply = self:GetOwner()
if IsValid(ply) then ply:RunLuaAnimation(FRAMES) end
for _, t in ipairs({0.2, 0.5}) do
timer.Simple(t, function()
if not IsValid(door) then return end
door:EmitSound("physics/wood/wood_crate_impact_hard1.wav", 80, math.random(120, 175))
end)
end
end
ply:RunLuaAnimation(FRAMES) проигрывает покадровую анимацию руки на третьем лице через библиотеку LuaAnim (lua/autorun/animations.lua): FRAMES — таблица вида { {время, {кость = угол, ...}}, ... }, зарегистрированная заранее через LuaAnim.Register("keys_knock", FRAMES).
3. Анимации вьюмодели
hard/soft/idle — это секвенции вьюмодели (v_keys.mdl), проигрываются через SendViewModelMatchingSequence, не через стандартный SWEP:Deploy/анимации оружия:
function SWEP:PlayAnim(name)
local vm = self:GetVM()
if not IsValid(vm) then return end
local seq = vm:LookupSequence(name)
if seq < 0 then return end
vm:SendViewModelMatchingSequence(seq)
vm:SetPlaybackRate(1)
return vm:SequenceDuration(seq)
end
SWEP:Think следит за self.Idle (временем окончания текущей секвенции) и возвращает вьюмодель в idle, когда анимация доиграла.