282 lines
8 KiB
Lua
282 lines
8 KiB
Lua
AddCSLuaFile()
|
|
DEFINE_BASECLASS( "base_wire_entity" )
|
|
ENT.PrintName = "Wire Analog Lever"
|
|
ENT.WireDebugName = "Lever"
|
|
|
|
function ENT:CalcAngle(shootPos, shootDir)
|
|
local myPos = self:GetPos()
|
|
local right = self:GetRight()
|
|
|
|
local planeHitPos = self:WorldToLocal(shootPos + shootDir * ((myPos - shootPos):Dot(right) / shootDir:Dot(right)))
|
|
|
|
self.Ang = math.Clamp( math.deg( math.atan2( planeHitPos[1], planeHitPos[3] ) ), -45, 45 )
|
|
end
|
|
|
|
if CLIENT then
|
|
|
|
function ENT:Initialize()
|
|
self.RBMin, self.RBMax = self:GetRenderBounds()
|
|
self.RBMin:Add(Vector(-30,0,0))
|
|
self.RBMax:Add(Vector(30,0,60))
|
|
end
|
|
|
|
local RenderGroup = ENT.RenderGroup
|
|
|
|
function ENT:CreateCSModel()
|
|
if self.csmodelLoading or IsValid(self.csmodel) then return end
|
|
self.csmodelLoading = true
|
|
timer.Simple(0, function()
|
|
if not IsValid(self) then return end
|
|
self.csmodel = octolib.createDummy("models/props_wasteland/tram_lever01.mdl", RenderGroup)
|
|
self.csmodel:SetParent(self)
|
|
self.NextRBUpdate = 0
|
|
self.csmodelLoading = false
|
|
end)
|
|
end
|
|
|
|
function ENT:Draw()
|
|
if not IsValid(self.csmodel) then
|
|
return self:CreateCSModel()
|
|
end
|
|
|
|
-- If user, calculate clientside, otherwise get server value
|
|
self.User = self:GetNWEntity("User",NULL)
|
|
if IsValid(self.User) then
|
|
self:CalcAngle(self.User:GetShootPos(), self.User:GetAimVector())
|
|
else
|
|
self.Ang = self:GetNWFloat("Ang",0) -- get networked ang
|
|
end
|
|
|
|
local lever_ang = Angle(self.Ang,0,0)
|
|
local ang = self:LocalToWorldAngles(lever_ang)
|
|
local pos = self:LocalToWorld(lever_ang:Up() * 21)
|
|
|
|
render.Model({
|
|
model = self.csmodel:GetModel(),
|
|
pos = pos,
|
|
angle = ang
|
|
}, self.csmodel)
|
|
|
|
BaseClass.Draw(self)
|
|
end
|
|
|
|
function ENT:Think()
|
|
if (CurTime() >= (self.NextRBUpdate or 0)) then
|
|
self.NextRBUpdate = CurTime() + 10
|
|
self:SetRenderBounds(self.RBMin, self.RBMax)
|
|
end
|
|
|
|
local isClicking = LocalPlayer():KeyDown(IN_USE) or LocalPlayer():KeyDown(IN_ATTACK)
|
|
if isClicking and not self.wasClicking and IsValid(self.csmodel) then
|
|
local aimPos = LocalPlayer():GetShootPos()
|
|
if aimPos:DistToSqr(self:GetPos())<100^2 then
|
|
local rayPos = util.IntersectRayWithOBB(
|
|
aimPos,
|
|
LocalPlayer():GetAimVector() * 100,
|
|
self.csmodel:GetPos(),
|
|
self.csmodel:GetAngles(),
|
|
self.csmodel:OBBMins() - Vector(2,2,2),
|
|
self.csmodel:OBBMaxs() + Vector(2,2,2)
|
|
)
|
|
if rayPos then
|
|
net.Start("wire_lever_activate")
|
|
net.WriteEntity(self)
|
|
net.SendToServer()
|
|
end
|
|
end
|
|
end
|
|
self.wasClicking = isClicking
|
|
|
|
-- Don't call baseclass think or else renderbounds will be overwritten
|
|
end
|
|
else
|
|
util.PrecacheModel( "models/props_wasteland/tram_lever01.mdl" )
|
|
|
|
function ENT:Initialize()
|
|
self:PhysicsInit( SOLID_VPHYSICS )
|
|
self:SetMoveType( MOVETYPE_VPHYSICS )
|
|
self:SetSolid( SOLID_VPHYSICS )
|
|
self:SetUseType( SIMPLE_USE )
|
|
|
|
self.Ang = 0
|
|
self.Value = 0
|
|
self:Setup(0, 1)
|
|
|
|
self.Inputs = WireLib.CreateInputs(self, {"SetValue", "Min", "Max"})
|
|
self.Outputs = WireLib.CreateOutputs(self, {"Value", "Entity [ENTITY]"})
|
|
end
|
|
|
|
function ENT:Setup(min, max)
|
|
min = min or 0
|
|
max = max or 1
|
|
self.Min = math.min(min, max)
|
|
self.Max = math.max(min, max)
|
|
end
|
|
|
|
function ENT:TriggerInput(iname, value)
|
|
if iname == "SetValue" then
|
|
self.Ang = (math.Clamp(value, self.Min, self.Max) - self.Min)/(self.Max - self.Min) * 90 - 45
|
|
elseif (iname == "Min") then
|
|
self.Min = value
|
|
elseif (iname == "Max") then
|
|
self.Max = value
|
|
end
|
|
end
|
|
|
|
function ENT:Use( ply )
|
|
if not IsValid(ply) or not ply:IsPlayer() or IsValid(self.User) then return end
|
|
self.User = ply
|
|
WireLib.TriggerOutput( self, "Entity", ply)
|
|
self:SetNWEntity("User",self.User)
|
|
end
|
|
|
|
util.AddNetworkString("wire_lever_activate")
|
|
net.Receive("wire_lever_activate", function(netlen, ply)
|
|
local ent = net.ReadEntity()
|
|
if not IsValid(ply) or not IsValid(ent) or not ent.Use or ent:GetClass() ~= "gmod_wire_lever" then return end
|
|
if IsValid(ent.User) then return end
|
|
|
|
if ply:GetShootPos():DistToSqr(ent:GetPos()) < 100^2 then
|
|
ent:Use(ply, ply, USE_ON, 1)
|
|
end
|
|
end)
|
|
|
|
function ENT:Think()
|
|
BaseClass.Think(self)
|
|
|
|
if IsValid(self.User) then
|
|
local shootPos = self.User:GetShootPos()
|
|
if shootPos:DistToSqr(self:GetPos()) < 100^2 and (self.User:KeyDown(IN_USE) or self.User:KeyDown(IN_ATTACK)) then
|
|
local shootDir = self.User:GetAimVector()
|
|
self:CalcAngle(shootPos, shootDir)
|
|
else
|
|
self.User = NULL
|
|
WireLib.TriggerOutput( self, "Entity", NULL)
|
|
self:SetNWEntity("User",self.User)
|
|
end
|
|
end
|
|
|
|
local oldvalue = self.Value
|
|
self.Value = Lerp((self.Ang + 45) / 90, self.Min, self.Max)
|
|
if self.Value ~= oldvalue then
|
|
WireLib.TriggerOutput(self, "Value", self.Value)
|
|
self:ShowOutput()
|
|
self:SetNWFloat("Ang",self.Ang)
|
|
end
|
|
|
|
self:NextThink(CurTime())
|
|
return true
|
|
end
|
|
|
|
function ENT:ShowOutput()
|
|
self:SetOverlayText(string.format("(%.2f - %.2f) = %.2f", self.Min, self.Max, self.Value))
|
|
end
|
|
|
|
function ENT:ConvertFromOldLever(base)
|
|
-- remove all constraints from self
|
|
self:SetParent()
|
|
constraint.RemoveAll(self)
|
|
|
|
local original_solid = self:GetSolid()
|
|
local original_motion = self:GetPhysicsObject():IsMotionEnabled()
|
|
|
|
-- remove collisions and freeze to prevent the entity from flying away
|
|
self:SetNotSolid(true)
|
|
self:GetPhysicsObject():EnableMotion(false)
|
|
|
|
-- change model and move into new position
|
|
self:SetModel("models/props_wasteland/tram_leverbase01.mdl")
|
|
self:PhysicsInit( SOLID_VPHYSICS )
|
|
self:SetPos(base:GetPos())
|
|
self:SetAngles(base:GetAngles())
|
|
|
|
timer.Simple(0,function() -- give the setpos time to be applied
|
|
if not IsValid(self) then return end
|
|
|
|
-- make copies of welds and nocollides and
|
|
-- move the constraints to self instead of base
|
|
-- we're only doing welds and nocollides to avoid any strange
|
|
-- issues, I think it's good enough :tm:
|
|
if base.Constraints then
|
|
for _, con in pairs( base.Constraints ) do
|
|
local Ent1 = con.Ent1
|
|
local Ent2 = con.Ent2
|
|
local Bone1 = con.Bone1
|
|
local Bone2 = con.Bone2
|
|
|
|
-- Move the target entity from base to self
|
|
if Ent1 == base then Ent1 = self
|
|
elseif Ent2 == base then Ent2 = self end
|
|
|
|
if con.Type == "Weld" then
|
|
local ForceLimit = con.forcelimit
|
|
local NoCollide = con.nocollide
|
|
local DeleteOnBreak = false -- can't be copied easily, so we'll assume it's false to save us the trouble
|
|
|
|
constraint.Weld(Ent1,Ent2,Bone1,Bone2,ForceLimit,NoCollide,DeleteOnBreak)
|
|
elseif con.Type == "NoCollide" then
|
|
constraint.NoCollide(Ent1,Ent2,Bone1,Bone2)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- copy parent
|
|
self:SetParent(base:GetParent())
|
|
base:Remove()
|
|
|
|
-- reset original values
|
|
self:SetNotSolid(not original_solid)
|
|
self:GetPhysicsObject():EnableMotion(original_motion)
|
|
end)
|
|
end
|
|
|
|
local fix_after_dupe = setmetatable({},{__mode="kv"})
|
|
hook.Add("AdvDupe_FinishPasting","LeverFixOldDupe",function(data)
|
|
if next(fix_after_dupe) == nil then return end
|
|
|
|
local levers = {}
|
|
for __, ent in pairs( data[1].CreatedEntities ) do
|
|
if ent:GetClass()=="gmod_wire_lever" then
|
|
levers[ent] = true
|
|
end
|
|
end
|
|
-- this hook is also called on garrydupe's paste, thanks to wirelib.lua
|
|
for self, base in pairs(fix_after_dupe) do
|
|
if base:IsValid() and self:IsValid() then
|
|
if levers[self] then
|
|
self:ConvertFromOldLever(base)
|
|
fix_after_dupe[self] = nil
|
|
end
|
|
else
|
|
if base:IsValid() then base:Remove() end
|
|
if self:IsValid() then self:Remove() end
|
|
fix_after_dupe[self] = nil
|
|
end
|
|
end
|
|
end)
|
|
|
|
function ENT:BuildDupeInfo()
|
|
local info = BaseClass.BuildDupeInfo(self) or {}
|
|
info.value = self.Value
|
|
return info
|
|
end
|
|
|
|
function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID)
|
|
BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID)
|
|
|
|
-- this is only used to update the entity to the latest version
|
|
-- if it's found to be an old dupe
|
|
if info.baseent then
|
|
local base = GetEntByID(info.baseent)
|
|
fix_after_dupe[self] = base
|
|
end
|
|
if info.value then
|
|
self.Value = nil -- So the value is dirty no matter what
|
|
self:TriggerInput("SetValue", info.value)
|
|
end
|
|
|
|
end
|
|
|
|
duplicator.RegisterEntityClass("gmod_wire_lever", WireLib.MakeWireEnt, "Data", "Min", "Max" )
|
|
|
|
end
|