dobrograd-13-06-2022/garrysmod/addons/feature-wire/lua/wire/tool_loader.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

654 lines
22 KiB
Lua

if SERVER then AddCSLuaFile() end
local function LoadTools()
-- load tools
for _, filename in pairs(file.Find("wire/stools/*.lua","LUA")) do
include("wire/stools/"..filename)
AddCSLuaFile("wire/stools/"..filename)
end
-- close last TOOL
if TOOL then WireToolSetup.close() end
end
-- prevent showing the ghost when poiting at any class in the TOOL.NoGhostOn table
local function NoGhostOn(self, trace)
return self.NoGhostOn and table.HasValue( self.NoGhostOn, trace.Entity:GetClass())
end
WireToolObj = {}
setmetatable( WireToolObj, ToolObj )
WireToolObj.Tab = "Wire"
-- optional LeftClick tool function for basic tools that just place/weld a device [default]
function WireToolObj:LeftClick( trace )
if not trace.HitPos or trace.Entity:IsPlayer() or trace.Entity:IsNPC() or (SERVER and not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone )) then return false end
if self.NoLeftOnClass and trace.HitNonWorld and (trace.Entity:GetClass() == self.WireClass or NoGhostOn(self, trace)) then return false end
if CLIENT then return true end
local ply = self:GetOwner()
local ent = self:LeftClick_Make( trace, ply ) -- WireToolObj.LeftClick_Make will be called if another function was not defined
return self:LeftClick_PostMake( ent, ply, trace )
end
if SERVER then
--
function WireToolObj:LeftClick_Make( trace, ply )
-- hit our own class, update
if self:CheckHitOwnClass(trace) then
self:LeftClick_Update(trace)
return true
end
local model = self:GetModel()
if not self:CheckMaxLimit() or not self:CheckValidModel(model) then return false end
local Ang = self:GetAngle( trace )
local ent = self:MakeEnt( ply, model, Ang, trace )
if IsValid(ent) then self:PostMake_SetPos( ent, trace ) end
return ent
end
-- Default MakeEnt function, override to use a different MakeWire* function
function WireToolObj:MakeEnt( ply, model, Ang, trace )
local ent = WireLib.MakeWireEnt( ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model}, self:GetConVars() )
if ent.RestoreNetworkVars then ent:RestoreNetworkVars(self:GetDataTables()) end
return ent
end
function WireToolObj:GetConVars() return end
function WireToolObj:GetDataTables() return {} end
--
-- to prevent update, set TOOL.NoLeftOnClass = true
function WireToolObj:LeftClick_Update( trace )
if trace.Entity.Setup then trace.Entity:Setup(self:GetConVars()) end
if trace.Entity.RestoreNetworkVars then trace.Entity:RestoreNetworkVars(self:GetDataTables()) end
end
--
-- this function needs to return true if the tool beam should be "fired"
function WireToolObj:LeftClick_PostMake( ent, ply, trace )
if ent == true then return true end
if ent == nil or ent == false or not ent:IsValid() then return false end
-- Parenting
local nocollide, const
if self:GetClientNumber( "parent" ) == 1 then
if (trace.Entity:IsValid()) then
-- Nocollide the gate to the prop to make adv duplicator (and normal duplicator) find it
if (!self.ClientConVar.noclip or self:GetClientNumber( "noclip" ) == 1) then
nocollide = constraint.NoCollide( ent, trace.Entity, 0,trace.PhysicsBone )
end
ent:SetParent( trace.Entity )
end
elseif not self:GetOwner():KeyDown(IN_WALK) then
-- Welding
const = WireLib.Weld( ent, trace.Entity, trace.PhysicsBone, true, false, self:GetOwner():GetInfo( "wire_tool_weldworld" )~="0" )
-- Nocollide All
if self:GetOwner():GetInfo( "wire_tool_nocollide" )~="0" then
ent:SetCollisionGroup( COLLISION_GROUP_WORLD )
end
end
undo.Create( self.WireClass )
undo.AddEntity( ent )
if (const) then undo.AddEntity( const ) end
if (nocollide) then undo.AddEntity( nocollide ) end
undo.SetPlayer( self:GetOwner() )
undo.Finish()
ply:AddCleanup( self.WireClass, ent )
if self.PostMake then self:PostMake(ent, ply, trace) end
duplicator.ApplyEntityModifiers(ply, ent)
return true
end
end
function WireToolObj:Reload( trace )
if not IsValid(trace.Entity) then return false end
if CLIENT then return true end
if self.ReloadSetsModel then
self:GetOwner():ConCommand(self.Mode.."_model " .. trace.Entity:GetModel())
self:GetOwner():PrintMessage( HUD_PRINTTALK, self.Name.." model set to " .. trace.Entity:GetModel() )
return true
end
if (trace.Entity:GetParent():IsValid()) then
-- Get its position
local pos = trace.Entity:GetPos()
-- Unparent
trace.Entity:SetParent()
-- Teleport it back to where it was before unparenting it (because unparenting causes issues which makes the gate teleport to random wierd places)
trace.Entity:SetPos( pos )
-- Wake
local phys = trace.Entity:GetPhysicsObject()
if (phys) then
phys:Wake()
end
-- Notify
self:GetOwner():ChatPrint("Entity unparented.")
return true
end
return false
end
-- basic UpdateGhost function that should cover most of wire's ghost updating needs [default]
function WireToolObj:UpdateGhost( ent )
if not IsValid(ent) then return end
local trace = self:GetOwner():GetEyeTrace()
if not trace.Hit then return end
-- don't draw the ghost if we hit nothing, a player, an npc, the type of device this tool makes, or any class this tool says not to
if IsValid(trace.Entity) and (trace.Entity:IsPlayer() or trace.Entity:IsNPC() or trace.Entity:GetClass() == self.WireClass or NoGhostOn(self, trace)) then
ent:SetNoDraw( true )
return
end
ent:SetAngles( self:GetAngle( trace ) )
self:SetPos( ent, trace )
--show the ghost
ent:SetNoDraw( false )
end
-- option tool Think function for updating the pos of the ghost and making one when needed [default]
function WireToolObj:Think()
local model = self:GetModel()
if not IsValid(self.GhostEntity) or self.GhostEntity:GetModel() ~= model then
if self.GetGhostAngle then -- the tool as a function for getting the proper angle for the ghost
self:MakeGhostEntity( model, Vector(0,0,0), self:GetGhostAngle(self:GetOwner():GetEyeTrace()) )
else -- the tool gives a fixed angle to add else use a zero'd angle
self:MakeGhostEntity( model, Vector(0,0,0), self.GhostAngle or Angle(0,0,0) )
end
if IsValid(self.GhostEntity) and CLIENT then self.GhostEntity:SetPredictable(true) end
end
self:UpdateGhost( self.GhostEntity )
end
function WireToolObj:CheckHitOwnClass( trace )
return trace.Entity:IsValid() and trace.Entity:GetClass() == self.WireClass
end
if SERVER then
function WireToolObj:CheckMaxLimit()
return self:GetSWEP():CheckLimit(self.MaxLimitName or (self.Mode.."s"))
end
end
-- Allow ragdolls to be used?
local ValidModelCache = {[""] = false}
function WireToolObj:CheckValidModel( model )
local val = ValidModelCache[model or ""]
if val~=nil then return val end
if SERVER then
ValidModelCache[model] = util.IsValidModel(model) and util.IsValidProp(model)
else
ValidModelCache[model] = file.Exists(model,"GAME") -- util.IsValidModel doesn't work clientside until after the server runs util.PrecacheModel
end
return ValidModelCache[model]
end
function WireToolObj:GetModel()
local model_convar = self:GetClientInfo( "model" )
if self.ClientConVar.modelsize then
local modelsize = self:GetClientInfo( "modelsize" )
if modelsize != "" then
local model = string.sub(model_convar, 1, -5) .."_".. modelsize .. string.sub(model_convar, -4)
if self:CheckValidModel(model) then return model end
model = string.GetPathFromFilename(model_convar) .. modelsize .."_".. string.GetFileFromFilename(model_convar)
if self:CheckValidModel(model) then return model end
end
end
if self:CheckValidModel(model_convar) then --use a valid model or the server crashes :<
return model_convar
end
return self.Model or self.ClientConVar.model or "models/props_c17/oildrum001.mdl"
end
function WireToolObj:GetAngle( trace )
local Ang
if math.abs(trace.HitNormal.x) < 0.001 and math.abs(trace.HitNormal.y) < 0.001 then
Ang = Vector(0,0,trace.HitNormal.z):Angle()
else
Ang = trace.HitNormal:Angle()
end
if self.GetGhostAngle then -- the tool as a function for getting the proper angle for the ghost
Ang = self:GetGhostAngle( trace )
elseif self.GhostAngle then -- the tool gives a fixed angle to add
Ang = Ang + self.GhostAngle
end
if self.ClientConVar.createflat then
-- Screen models need a bit of adjustment
if self:GetClientNumber("createflat") == 0 then
Ang.pitch = Ang.pitch + 90
end
local model = self:GetModel()
if string.find(model, "pcb") or string.find(model, "hunter") then
-- PHX Screen models should thus be +180 when not flat, +90 when flat
Ang.pitch = Ang.pitch + 90
end
else
Ang.pitch = Ang.pitch + 90
end
return Ang
end
function WireToolObj:SetPos( ent, trace )
-- move the ghost to aline properly to where the device will be made
local min = ent:OBBMins()
if self.GetGhostMin then -- tool has a function for getting the min
ent:SetPos( trace.HitPos - trace.HitNormal * self:GetGhostMin( min, trace ) )
elseif self.GhostMin then -- tool gives the axis for the OBBmin to use
ent:SetPos( trace.HitPos - trace.HitNormal * min[self.GhostMin] )
elseif self.ClientConVar.createflat and (self:GetClientNumber("createflat") == 1) ~= ((string.find(self:GetModel(), "pcb") or string.find(self:GetModel(), "hunter")) ~= nil) then
-- Screens have odd models. If createflat is 1, or its 0 and its a PHX model, use max.x
ent:SetPos( trace.HitPos + trace.HitNormal * ent:OBBMaxs().x )
else -- default to the z OBBmin
ent:SetPos( trace.HitPos - trace.HitNormal * min.z )
end
end
if SERVER then WireToolObj.PostMake_SetPos = WireToolObj.SetPos end
if CLIENT then
local fonttab = {font = "Helvetica", size = 60, weight = 900}
for size=60,20,-2 do
fonttab.size = size
surface.CreateFont("GmodToolScreen"..size, fonttab)
end
local iconparams = {
["$vertexcolor"] = 1,
["$vertexalpha"] = 1,
["$ignorez"] = 1 -- This is essential, since the base Gmod screen_bg has ignorez, otherwise it'll draw overtop of us
}
local txBackground = surface.GetTextureID("models/weapons/v_toolgun/wirescreen_bg")
function WireToolObj:DrawToolScreen(width, height)
surface.SetTexture(txBackground)
surface.DrawTexturedRect(0, 0, width, height)
local text = self.Name
if self.ScreenFont then
surface.SetFont(self.ScreenFont)
else
for size=60,20,-2 do
surface.SetFont("GmodToolScreen"..size)
local x,y = surface.GetTextSize(text)
if x <= (width - 16) then
self.ScreenFont = "GmodToolScreen"..size
break
end
end
end
local w, h = surface.GetTextSize(text)
local x = width/2 - w/2
local y = 105 - h/2
-- Draw shadow first
surface.SetTextColor(0, 0, 0, 255)
surface.SetTextPos(x + 3, y + 3)
surface.DrawText(text)
surface.SetTextColor(255, 255, 255, 255)
surface.SetTextPos(x, y)
surface.DrawText(text)
iconparams[ "$basetexture" ] = "spawnicons/"..self:GetModel():sub(1,-5)
local mat = CreateMaterial(self:GetModel() .. "_DImage", "UnlitGeneric", iconparams )
surface.SetMaterial(mat)
surface.DrawTexturedRect( 128 - 32, 150, 64, 64)
local on = self:GetOwner():GetInfo( "wire_tool_weldworld" )~="0" and not self:GetOwner():KeyDown(IN_WALK)
draw.DrawText("World Weld: "..(on and "On" or "Off"),
"GmodToolScreen20",
5, height-38,
Color(on and 150 or 255, on and 255 or 150, 150, 255)
)
local on = self:GetOwner():GetInfo( "wire_tool_nocollide" )~="0" and not self:GetOwner():KeyDown(IN_WALK)
draw.DrawText("Nocollide All: "..(on and "On" or "Off"),
"GmodToolScreen20",
5, height-22,
Color(on and 150 or 255, on and 255 or 150, 150, 255)
)
end
CreateClientConVar( "wire_tool_weldworld", "0", true, true )
CreateClientConVar( "wire_tool_nocollide", "1", true, true )
local function CreateCPanel_WireOptions( Panel )
Panel:ClearControls()
Panel:Help("Hold Alt while spawning Wire entities\nto disable Weld and Nocollide All")
Panel:CheckBox("Allow Weld to World", "wire_tool_weldworld")
Panel:CheckBox("Nocollide All", "wire_tool_nocollide")
end
hook.Add("PopulateToolMenu","WireLib_WireOptions",function()
spawnmenu.AddToolMenuOption( "Wire", "Options", "WireOptions", "Tool Options", "", "", CreateCPanel_WireOptions, nil )
end)
end
-- function used by TOOL.BuildCPanel
WireToolHelpers = {}
if CLIENT then
-- gets the TOOL since TOOL.BuildCPanel isn't passed this var. wts >_<
function WireToolHelpers.GetTOOL(mode)
for _,wep in ipairs(LocalPlayer():GetWeapons()) do
if wep:GetClass() == "gmod_tool" then
return wep:GetToolObject(mode)
end
end
end
-- makes the preset control for use cause we're lazy
function WireToolHelpers.MakePresetControl(panel, mode, folder)
if not mode or not panel then return end
local TOOL = WireToolHelpers.GetTOOL(mode)
if not TOOL then return end
local ctrl = vgui.Create( "ControlPresets", panel )
ctrl:SetPreset(folder or mode)
if TOOL.ClientConVar then
local options = {}
for k, v in pairs(TOOL.ClientConVar) do
if k ~= "id" then
k = mode.."_"..k
options[k] = v
ctrl:AddConVar(k)
end
end
ctrl:AddOption("#Default", options)
end
panel:AddPanel( ctrl )
end
function WireToolHelpers.MakeModelSizer(panel, convar)
return panel:AddControl("ListBox", {
Label = "Model Size",
Options = {
["normal"] = { [convar] = "" },
["mini"] = { [convar] = "mini" },
["nano"] = { [convar] = "nano" }
}
})
end
-- adds the neato model select control
function WireToolHelpers.MakeModelSel(panel, mode)
local TOOL = WireToolHelpers.GetTOOL(mode)
if not TOOL then return end
ModelPlug_AddToCPanel(panel, TOOL.short_name, TOOL.Mode, true)
end
end
function WireToolHelpers.SetupSingleplayerClickHacks(TOOL) end -- empty stub outside of Singleplayer
if game.SinglePlayer() then -- wtfgarry
-- In Singleplayer, "Because its Predicted", LeftClick/RightClick/Reload don't fire Clientside. Lets work around that
if SERVER then
util.AddNetworkString("wire_singleplayer_tool_wtfgarry")
local function send_singleplayer_click(ply, funcname, toolname)
net.Start("wire_singleplayer_tool_wtfgarry")
net.WriteString(funcname)
net.WriteString(toolname)
net.Send(ply)
end
function WireToolHelpers.SetupSingleplayerClickHacks(TOOL)
local originalLeftClick = TOOL.LeftClick
function TOOL:LeftClick(trace)
send_singleplayer_click(self:GetOwner(), "LeftClick", TOOL.Mode)
return originalLeftClick(self, trace)
end
local originalRightClick = TOOL.RightClick
function TOOL:RightClick(trace)
send_singleplayer_click(self:GetOwner(), "RightClick", TOOL.Mode)
return originalRightClick(self, trace)
end
local originalReload = TOOL.Reload
function TOOL:Reload(trace)
send_singleplayer_click(self:GetOwner(), "Reload", TOOL.Mode)
return originalReload(self, trace)
end
end
elseif CLIENT then
net.Receive( "wire_singleplayer_tool_wtfgarry", function(len)
local funcname = net.ReadString()
local toolname = net.ReadString()
local tool = WireToolHelpers.GetTOOL(toolname)
if not tool then return end
tool[funcname](tool, LocalPlayer():GetEyeTrace())
end)
end
end
WireToolSetup = {}
-- sets the ToolCategory for every wire tool made fallowing its call
function WireToolSetup.setCategory( s_cat, ... )
WireToolSetup.cat = s_cat
local categories = {...}
if #categories > 0 then
WireToolSetup.Wire_MultiCategories = categories
else
WireToolSetup.Wire_MultiCategories = nil
end
end
-- Sets the icon for the current tool
function WireToolSetup.setToolMenuIcon( icon )
if SERVER then return end
TOOL.Wire_ToolMenuIcon = icon
end
-- makes a new TOOL
-- s_mode: Tool_mode, same as the old tool lua file name, minus the "wire_" part
-- s_name: Proper name for the tool
-- s_class: For tools that make a device. Should begin with "gmod_wire_". Can be nil if not using WireToolObj.LeftClick or WireToolSetup.BaseLang
-- f_toolmakeent: Server side function for making the tools device. Can be nil if not using WireToolObj.LeftClick
function WireToolSetup.open( s_mode, s_name, s_class, f_toolmakeent, s_pluralname )
-- close the previous TOOL if not done so already
if TOOL then WireToolSetup.close() end
-- make new TOOL object
TOOL = WireToolObj:Create()
-- default vars,
TOOL.Mode = "wire_"..s_mode
TOOL.short_name = s_mode
TOOL.Category = WireToolSetup.cat
TOOL.Wire_MultiCategories = WireToolSetup.Wire_MultiCategories
TOOL.Name = s_name
TOOL.PluralName = s_pluralname
TOOL.WireClass = s_class
if f_toolmakeent then
TOOL.LeftClick_Make = f_toolmakeent
end
local info = debug.getinfo(2, "S")
if info then
TOOL.SourceFile = info.short_src
end
end
-- closes and saves the open TOOL obj
function WireToolSetup.close()
TOOL:CreateConVars()
SWEP.Tool[TOOL.Mode] = TOOL
TOOL = nil
end
-- optional function to add the basic language for basic tools
function WireToolSetup.BaseLang()
if CLIENT then
language.Add( "undone_"..TOOL.WireClass, "Undone Wire "..TOOL.Name )
if TOOL.PluralName then
language.Add( "Cleanup_"..TOOL.WireClass, "Wire "..TOOL.PluralName )
language.Add( "Cleaned_"..TOOL.WireClass, "Cleaned Up Wire "..TOOL.PluralName )
end
for _, info in pairs(TOOL.Information or {}) do
if info.text then
language.Add("Tool." .. TOOL.Mode .. "." .. info.name, info.text)
end
end
end
cleanup.Register(TOOL.WireClass)
end
function WireToolSetup.SetupMax( i_limit, s_maxlimitname , s_warning )
TOOL.MaxLimitName = s_maxlimitname or TOOL.WireClass:sub(6).."s"
s_warning = s_warning or "You've hit the Wire "..TOOL.PluralName.." limit!"
if CLIENT then
language.Add("SBoxLimit_"..TOOL.MaxLimitName, s_warning)
AddWireAdminMaxDevice(TOOL.PluralName, TOOL.MaxLimitName)
else
CreateConVar("sbox_max"..TOOL.MaxLimitName, i_limit)
end
end
-- Sets up a tool with RightClick, Reload, and DrawHUD functions that link/unlink entities
-- The SENT should have ENT:LinkEnt(e), ENT:UnlinkEnt(e), and ENT:ClearEntities()
-- It should also send ENT.Marks to the client via WireLib.SendMarks(ent)
-- Pass it true to disable linking multiple entities (ie for Pod Controllers)
function WireToolSetup.SetupLinking(SingleLink, linkedname)
TOOL.SingleLink = SingleLink
linkedname = linkedname or "entity"
if CLIENT then
if TOOL.Information == nil or next(TOOL.Information) == nil then
TOOL.Information = {
{ name = "left_0", stage = 0 },
{ name = "right_0", stage = 0 },
{ name = "reload_0", stage = 0 },
{ name = "right_1", stage = 1 },
{ name = "right_2", stage = 2 },
}
if not SingleLink then
table.insert(TOOL.Information, { name = "info_1", stage = 1 })
table.insert(TOOL.Information, { name = "info_2", stage = 2 })
table.insert(TOOL.Information, { name = "reload_2", stage = 2 })
end
end
language.Add( "Tool."..TOOL.Mode..".left_0", "Create/Update "..TOOL.Name )
language.Add( "Tool."..TOOL.Mode..".right_0", "Select a " .. TOOL.Name .. " to link" )
language.Add( "Tool."..TOOL.Mode..".reload_0", "Unlink everything from a " .. TOOL.Name )
language.Add( "Tool."..TOOL.Mode..".right_1", "Now select the " .. linkedname .. " to link to" )
language.Add( "Tool."..TOOL.Mode..".right_2", "Now select the " .. linkedname .. " to unlink" )
if not SingleLink then
language.Add( "Tool."..TOOL.Mode..".info_1", "Hold shift to link to more")
language.Add( "Tool."..TOOL.Mode..".info_2", "Hold shift to unlink from more")
language.Add( "Tool."..TOOL.Mode..".reload_2", "Reload on the same controller again to clear all linked entities.")
end
function TOOL:DrawHUD()
local trace = self:GetOwner():GetEyeTrace()
if self:CheckHitOwnClass(trace) and trace.Entity.Marks then
local markerpos = trace.Entity:GetPos():ToScreen()
for _, ent in pairs(trace.Entity.Marks) do
if IsValid(ent) then
local markpos = ent:GetPos():ToScreen()
surface.SetDrawColor( 255,255,100,255 )
surface.DrawLine( markerpos.x, markerpos.y, markpos.x, markpos.y )
end
end
end
end
end
function TOOL:RightClick(trace)
if not trace.HitPos or not IsValid(trace.Entity) or trace.Entity:IsPlayer() then return false end
if CLIENT then return true end
local ent = trace.Entity
if self:GetStage() == 0 then -- stage 0: right-clicking on our own class selects it
if self:CheckHitOwnClass(trace) then
self.Controller = ent
self:SetStage(1)
return true
else
return false
end
elseif self:GetStage() == 1 then -- stage 1: right-clicking on something links it
if not IsValid(self.Controller) then self:SetStage(0) return end
local ply = self:GetOwner()
local success, message = self.Controller:LinkEnt(ent)
if success then
if self.SingleLink or not ply:KeyDown(IN_SPEED) then self:SetStage(0) end
self.HasLinked = true
WireLib.AddNotify(ply, "Linked entity: " .. tostring(ent) .. " to the "..self.Name, NOTIFY_GENERIC, 5)
else
WireLib.AddNotify(ply, message or "That entity is already linked to the "..self.Name, NOTIFY_ERROR, 5, NOTIFYSOUND_DRIP3)
end
return success
end
end
function TOOL:Reload(trace)
if not trace.HitPos or not IsValid(trace.Entity) or trace.Entity:IsPlayer() then
self:SetStage(0)
return false
end
if CLIENT then return true end
local ent = trace.Entity
if self:CheckHitOwnClass(trace) then -- regardless of stage, reloading on our own class clears it
local ply = self:GetOwner()
self:SetStage(0)
if ent.ClearEntities then
ent:ClearEntities()
WireLib.AddNotify(ply, "All entities unlinked from the "..self.Name, NOTIFY_GENERIC, 7)
else
ent:UnlinkEnt()
WireLib.AddNotify(ply, "Unlinked "..self.Name, NOTIFY_GENERIC, 5)
end
return true
elseif self:GetStage() == 1 then -- stage 1: reloading on something else unlinks it
local ply = self:GetOwner()
local success, message = self.Controller:UnlinkEnt(ent)
if success then
if not self:GetOwner():KeyDown(IN_SPEED) then self:SetStage(0) end
self.HasLinked = true
WireLib.AddNotify(ply, "Unlinked entity: " .. tostring(ent) .. " from the "..self.Name, NOTIFY_GENERIC, 5)
else
WireLib.AddNotify(ply, message or "That entity is not linked to the "..self.Name, NOTIFY_ERROR, 5, NOTIFYSOUND_DRIP3)
end
return success
end
end
if not SingleLink then
function TOOL:Think()
if self.HasLinked then
if not self:GetOwner():KeyDown(IN_SPEED) then self:SetStage(0) end
if self:GetStage() == 0 then self.HasLinked = false end
end
WireToolObj.Think(self) -- Basic ghost
end
end
end
LoadTools()