568 lines
13 KiB
Lua
568 lines
13 KiB
Lua
module("luadev",package.seeall)
|
|
Tag=_NAME..'1'
|
|
|
|
--net_retdata = Tag..'_retdata'
|
|
|
|
if SERVER then
|
|
util.AddNetworkString(Tag)
|
|
--util.AddNetworkString(net_retdata)
|
|
end
|
|
|
|
|
|
-- Enums
|
|
|
|
local enums={
|
|
TO_CLIENTS=1,
|
|
TO_CLIENT=2,
|
|
TO_SERVER=3,
|
|
TO_SHARED=4,
|
|
}
|
|
|
|
local revenums={} -- lookup
|
|
_M.revenums=revenums
|
|
|
|
for k,v in pairs(enums) do
|
|
_M[k]=v
|
|
revenums[v]=k
|
|
end
|
|
|
|
STAGE_PREPROCESS=1
|
|
STAGE_COMPILED=2
|
|
STAGE_POST=3
|
|
STAGE_PREPROCESSING=4
|
|
|
|
-- Figure out what to put to extra table
|
|
function MakeExtras(pl,extrat)
|
|
if pl and isentity(pl) and pl:IsPlayer() then
|
|
extrat = extrat or {}
|
|
extrat.ply = pl
|
|
end
|
|
return extrat
|
|
end
|
|
|
|
-- Helpers
|
|
|
|
function TransmitHook(stage,...)
|
|
return hook.Run("LuaDevTransmit",stage,...)
|
|
end
|
|
|
|
function IsOneLiner(script)
|
|
return script and not script:find("\n",1,true)
|
|
end
|
|
|
|
function GiveFileContent(fullpath,searchpath)
|
|
--Print("Reading: "..tostring(fullpath))
|
|
if fullpath==nil or fullpath=="" then return false end
|
|
|
|
local content=file.Read(fullpath,searchpath or "MOD")
|
|
if content==0 then return false end
|
|
return content
|
|
end
|
|
|
|
function TableToString(tbl)
|
|
return string.Implode(" ",tbl)
|
|
end
|
|
|
|
function Print(...)
|
|
Msg("[Luadev"..(SERVER and ' Server' or '').."] ")
|
|
print(...)
|
|
end
|
|
|
|
if CLIENT then
|
|
luadev_store = CreateClientConVar( "luadev_store", "1",true)
|
|
function ShouldStore()
|
|
return luadev_store:GetBool()
|
|
end
|
|
end
|
|
|
|
if CLIENT then
|
|
luadev_verbose = CreateClientConVar( "luadev_verbose", "1",true)
|
|
else
|
|
luadev_verbose = CreateConVar( "luadev_verbose", "1", { FCVAR_NOTIFY ,FCVAR_ARCHIVE} )
|
|
end
|
|
function Verbose(lev)
|
|
return (luadev_verbose:GetInt() or 99)>=(lev or 1)
|
|
end
|
|
|
|
function PrintX(script,...)
|
|
local oneline = IsOneLiner(script) and 2
|
|
local verb = Verbose(oneline)
|
|
local Msg=not verb and _Msg or Msg
|
|
local print=not verb and _print or print
|
|
Msg("[Luadev"..(SERVER and ' Server' or '').."] ")
|
|
print(...)
|
|
end
|
|
|
|
specials = {
|
|
swep = {
|
|
function(val,extra,script,info)
|
|
local SWEP=weapons.GetStored(val)
|
|
if not SWEP then
|
|
SWEP = {Primary={}, Secondary={},Base = "weapon_base",ClassName = val, Folder = 'weapons/'..val }
|
|
end
|
|
_G.SWEP = SWEP
|
|
end,
|
|
function(val,extra,script,info)
|
|
local tbl = _G.SWEP
|
|
_G.SWEP = nil
|
|
if istable(tbl) then
|
|
--local table_ForEach=table.ForEach table.ForEach=function()end timer.Simple(0,function() table.ForEach=table_ForEach end)
|
|
if Verbose() then
|
|
Print("Registering weapon "..tostring(val))
|
|
end
|
|
weapons.Register(tbl, val, true)
|
|
--table.ForEach=table_ForEach
|
|
end
|
|
end,
|
|
},
|
|
sent = {
|
|
function(val,extra,script,info)
|
|
local ENT=scripted_ents.GetStored(val)
|
|
if ENT and ENT.t then
|
|
ENT=ENT.t
|
|
else
|
|
ENT = {ClassName=val , Folder = 'entities/'..val}
|
|
end
|
|
_G.ENT = ENT
|
|
end,
|
|
function(val,extra,script,info)
|
|
local tbl = _G.ENT
|
|
_G.ENT = nil
|
|
if istable(tbl) then
|
|
|
|
tbl.Model = tbl.Model or Model("models/props_borealis/bluebarrel001.mdl")
|
|
if not tbl.Base then
|
|
tbl.Base = "base_anim"
|
|
tbl.Type = tbl.Type or "anim"
|
|
end
|
|
if Verbose() then
|
|
Print("Registering entity "..tostring(val))
|
|
end
|
|
scripted_ents.Register(tbl, val)
|
|
end
|
|
end,
|
|
},
|
|
stool = {
|
|
function(val,extra,script,info)
|
|
local gmod_tool=weapons.GetStored("gmod_tool")
|
|
if gmod_tool and gmod_tool.Tool and gmod_tool.Tool[val] then
|
|
TOOL=gmod_tool.Tool[val]
|
|
assert(TOOL and TOOL.Mode == val)
|
|
else
|
|
|
|
assert(ToolObj,"Need ToolObj from gamemode to create new tools")
|
|
|
|
TOOL = ToolObj:Create(toolmode)
|
|
TOOL.Mode = toolmode
|
|
|
|
end
|
|
|
|
_G.TOOL = TOOL
|
|
end,
|
|
function(val,extra,script,info)
|
|
local tbl = _G.TOOL
|
|
_G.TOOL = nil
|
|
if not istable(tbl) then return end
|
|
|
|
Print("Registering tool "..tostring(val))
|
|
|
|
if tbl.CreateConVars then
|
|
tbl:CreateConVars()
|
|
end
|
|
|
|
local gmod_tool=weapons.GetStored("gmod_tool")
|
|
if TOOL and gmod_tool and gmod_tool.Tool then
|
|
gmod_tool.Tool[val] = TOOL
|
|
end
|
|
|
|
|
|
end,
|
|
},
|
|
-- TODO --
|
|
effect = {
|
|
function(val,extra,script,info)
|
|
if SERVER then return end
|
|
_G.EFFECT = {ClassName=val,Folder = 'effects/'..val }
|
|
end,
|
|
function(val,extra,script,info)
|
|
if Verbose() then
|
|
Print("Registering effect "..tostring(val))
|
|
end
|
|
if CLIENT then
|
|
local tbl = _G.EFFECT _G.EFFECT = nil
|
|
if tbl then
|
|
effects.Register(_G.EFFECT,val)
|
|
end
|
|
end
|
|
end,
|
|
},
|
|
}
|
|
local specials = specials
|
|
|
|
|
|
function ProcessSpecial(mode,script,info,extra)
|
|
|
|
if not extra then return end
|
|
for special_type,funcs in next,specials do
|
|
local val = extra[special_type]
|
|
if val then
|
|
if Verbose(10) then
|
|
Print("ProcessSpecial",mode,special_type," -> ",val)
|
|
end
|
|
local func = funcs[mode]
|
|
if func then return func(val,extra,script,info) end
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
function FindPlayer(plyid)
|
|
if not plyid or not isstring(plyid) then return end
|
|
|
|
local cl
|
|
for k,v in pairs(player.GetAll()) do
|
|
if v:SteamID()==plyid or tostring(v:AccountID())==plyid or tostring(v:UserID())==plyid then
|
|
cl=v
|
|
break
|
|
end
|
|
end
|
|
if !cl then
|
|
for k,v in pairs(player.GetAll()) do
|
|
if v:Name():lower():find(plyid:lower(),1,true)==1 then
|
|
cl=v
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if !cl then
|
|
for k,v in pairs(player.GetAll()) do
|
|
if string.find(v:Name(),plyid) then
|
|
cl=v
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if !cl then
|
|
for k,v in pairs(player.GetAll()) do
|
|
if v:Name():lower():find(plyid:lower(),1,true) then
|
|
cl=v
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if !cl and easylua and easylua.FindEntity then
|
|
cl = easylua.FindEntity(plyid)
|
|
end
|
|
return IsValid(cl) and cl and cl:IsPlayer() or nil
|
|
end
|
|
|
|
|
|
-- Watch system
|
|
|
|
function FileTime(fullpath,searchpath)
|
|
--Print("Reading: "..tostring(fullpath))
|
|
if fullpath==nil or fullpath=="" then return false end
|
|
|
|
local t=file.Time(fullpath,searchpath or "MOD")
|
|
|
|
if not t or t==0 then return false end
|
|
|
|
return t
|
|
end
|
|
|
|
local watchlist = rawget(_M,"GetWatchList") and GetWatchList() or {} function GetWatchList() return watchlist end
|
|
local i=0
|
|
hook.Add("Think",Tag.."_watchlist",function()
|
|
if not watchlist[1] then return end
|
|
|
|
i=i+1
|
|
local entry = watchlist[i]
|
|
if not entry then
|
|
i=0
|
|
entry = watchlist[1]
|
|
if not entry then return end
|
|
end
|
|
|
|
local newtime = FileTime(entry.path,entry.searchpath)
|
|
local oldtime = entry.time
|
|
if newtime and newtime~=oldtime then
|
|
|
|
entry.time = newtime
|
|
|
|
Msg"[LuaDev] Refresh " print(unpack(entry.cmd))
|
|
|
|
RunConsoleCommand(unpack(entry.cmd))
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
-- compression
|
|
|
|
function Compress( data )
|
|
return util.Compress( data )
|
|
end
|
|
|
|
function Decompress(data)
|
|
return util.Decompress( data )
|
|
end
|
|
|
|
function WriteCompressed(data)
|
|
if #data==0 then
|
|
net.WriteUInt( 0, 24 )
|
|
return false
|
|
end
|
|
|
|
local compressed = Compress( data )
|
|
local len = compressed:len()
|
|
net.WriteUInt( len, 24 )
|
|
net.WriteData( compressed, len )
|
|
return compressed
|
|
end
|
|
|
|
function ReadCompressed()
|
|
local len = net.ReadUInt( 24 )
|
|
if len==0 then return "" end
|
|
|
|
return Decompress( net.ReadData( len ) )
|
|
end
|
|
|
|
-- Compiler / runner
|
|
local function ValidCode(src,who)
|
|
local ret = CompileString(src,who or "",false)
|
|
if type(ret)=='string' then
|
|
return nil,ret
|
|
end
|
|
return ret or true
|
|
end
|
|
_M.ValidScript=ValidCode
|
|
_M.ValidCode=ValidCode
|
|
|
|
function ProcessHook(stage,...)
|
|
return hook.Run("LuaDevProcess",stage,...)
|
|
end
|
|
local LuaDevProcess=ProcessHook
|
|
|
|
local LUADEV_EXECUTE_STRING=RunStringEx
|
|
local LUADEV_EXECUTE_FUNCTION=xpcall
|
|
local LUADEV_COMPILE_STRING=CompileString
|
|
local mt= {
|
|
__tostring=function(self) return self[1] end,
|
|
|
|
__index={
|
|
set=function(self,what) self[1]=what end,
|
|
get=function(self,what) return self[1] end,
|
|
},
|
|
--__newindex=function(self,what) rawset(self,1,what) end,
|
|
}
|
|
local strobj=setmetatable({""},mt)
|
|
|
|
function Run(script,info,extra)
|
|
--compat
|
|
if CLIENT and not extra and info and istable(info) then
|
|
return luadev.RunOnSelf(script,"COMPAT",{ply=info.ply})
|
|
end
|
|
|
|
info = info or "??ANONYMOUS??"
|
|
if not isstring(info) then
|
|
debug.Trace()
|
|
ErrorNoHalt("LuaDev Warning: info type mismatch: "..type(info)..': '..tostring(info))
|
|
end
|
|
|
|
-- STAGE_PREPROCESS
|
|
local ret,newinfo = LuaDevProcess(STAGE_PREPROCESS,script,info,extra,nil)
|
|
|
|
if ret == false then return end
|
|
if ret ~=nil and ret~=true then script = ret end
|
|
|
|
if newinfo then info = newinfo end
|
|
|
|
-- STAGE_PREPROCESSING
|
|
rawset(strobj,1,script)
|
|
local ret = LuaDevProcess(STAGE_PREPROCESSING,strobj,info,extra,nil)
|
|
script = rawget(strobj,1)
|
|
|
|
if not script then
|
|
return false,"no script"
|
|
end
|
|
|
|
-- Compiling
|
|
|
|
local func = LUADEV_COMPILE_STRING(script,tostring(info),false)
|
|
if not func or isstring( func ) then compileerr = func or true func = false end
|
|
|
|
local ret = LuaDevProcess(STAGE_COMPILED,script,info,extra,func)
|
|
-- replace function
|
|
if ret == false then return end
|
|
if ret ~=nil and isfunction(ret) then
|
|
func = ret
|
|
compileerr = false
|
|
end
|
|
|
|
if not func then
|
|
if compileerr then
|
|
return false,"Syntax error: "..tostring(compileerr)
|
|
end
|
|
end
|
|
|
|
lastextra = extra
|
|
lastinfo = info
|
|
lastscript = script
|
|
lastfunc = func
|
|
|
|
ProcessSpecial(1,script,info,extra)
|
|
|
|
local args = extra and extra.args and (istable(extra.args) and extra.args or {extra.args})
|
|
if not args then args=nil end
|
|
|
|
|
|
-- Run the stuff
|
|
-- because garry's runstring has social engineer sexploits and such
|
|
local errormessage
|
|
local function LUADEV_TRACEBACK(errmsg)
|
|
errormessage = errmsg
|
|
local tracestr = debug.traceback(errmsg,2)
|
|
|
|
-- Tidy up the damn long trace
|
|
local p1=tracestr:find("LUADEV_EXECUTE_FUNCTION",1,true)
|
|
if p1 then
|
|
local p2=0
|
|
while p2 and p2<p1 do
|
|
local new=tracestr:find("\n",p2+1,true)
|
|
|
|
if new>p1 then
|
|
tracestr=tracestr:sub(1,new)
|
|
break
|
|
end
|
|
p2=new
|
|
end
|
|
end
|
|
|
|
ErrorNoHalt('[ERROR] '..tracestr )-- ..'\n')
|
|
end
|
|
|
|
local LUADEV_EXECUTE_FUNCTION=xpcall
|
|
local returnvals = {LUADEV_EXECUTE_FUNCTION(func,LUADEV_TRACEBACK,args and unpack(args) or nil)}
|
|
local ok = returnvals[1] table.remove(returnvals,1)
|
|
|
|
-- STAGE_POST
|
|
local ret = LuaDevProcess(STAGE_POST,script,info,extra,func,args,ok,returnvals)
|
|
ProcessSpecial(2,script,info,extra)
|
|
|
|
if not ok then
|
|
return false,errormessage
|
|
end
|
|
|
|
return ok,returnvals
|
|
end
|
|
|
|
|
|
function RealFilePath(name)
|
|
local searchpath = "MOD"
|
|
|
|
local RelativePath='lua/'..name
|
|
|
|
if name:find("^lua/") then -- search cache
|
|
name=name:gsub("^lua/","")
|
|
RelativePath=name
|
|
searchpath = "LUA"
|
|
elseif name:find("^%.%./") then -- whole shit
|
|
name=name:gsub("^%.%./","")
|
|
RelativePath=name
|
|
elseif name:find("^data/") then -- whatever
|
|
name=name:gsub("^data/","")
|
|
RelativePath='data/'..name
|
|
end
|
|
|
|
if !file.Exists(RelativePath,searchpath) then return nil end
|
|
return RelativePath,searchpath
|
|
end
|
|
|
|
|
|
function AutoComplete(cmd,commandName,args)
|
|
|
|
local name = string.Explode(' ',args)
|
|
|
|
name=name[#name] or ""
|
|
|
|
local path = string.GetPathFromFilename(name)
|
|
|
|
local searchpath = "MOD"
|
|
|
|
local RelativePath='lua/'..(name or "")
|
|
|
|
if name:find("^lua/") then -- search cache
|
|
name=name:gsub("^lua/","")
|
|
RelativePath=name
|
|
searchpath = "LUA"
|
|
elseif name:find("^%.%./") then -- whole shit
|
|
name=name:gsub("^%.%./","")
|
|
RelativePath=name
|
|
elseif name:find("^data/") then -- whatever
|
|
name=name:gsub("^data/","")
|
|
RelativePath='data/'..name
|
|
end
|
|
|
|
local searchstr = RelativePath.."*"
|
|
|
|
local files,folders=file.Find(searchstr,searchpath or "MOD")
|
|
files=files or {}
|
|
folders=folders or {}
|
|
for k,v in pairs(folders) do
|
|
table.insert(files,v)
|
|
end
|
|
local candidates=files
|
|
candidates=candidates or {}
|
|
for i,_ in pairs(candidates) do
|
|
candidates[i]=commandName.." "..path..candidates[i]
|
|
end
|
|
|
|
return candidates
|
|
|
|
end
|
|
|
|
local sv_allowcslua = GetConVar 'sv_allowcslua'
|
|
|
|
function CanLuaDev(ply,script,command,target,target_ply,extra)
|
|
local ret,x = hook.Run("CanLuaDev",ply,script,command,target,target_ply,extra)
|
|
if ret~=nil then return ret,x end
|
|
local ret,x = hook.Run("LuaDevIsPlayerAllowed", ply, script or "")
|
|
if ret~=nil then return ret,x end
|
|
if ply:query("LuaDev") then return true end
|
|
if target == TO_CLIENT and
|
|
(target_ply == ply
|
|
or (target_ply
|
|
and istable(target_ply)
|
|
and target_ply[1]==ply
|
|
and table.Count(target_ply)==1))
|
|
then
|
|
if sv_allowcslua:GetBool() then return true end
|
|
end
|
|
end
|
|
|
|
function RejectCommand(pl,x)
|
|
S2C(pl,"No Access"..(x and (": "..tostring(x)) or ""))
|
|
end
|
|
|
|
function COMMAND(str,func,complete)
|
|
if SERVER then
|
|
concommand.Add('lua_'..str,function(pl,command,cmds,strcmd)
|
|
local id=pl
|
|
if IsValid(pl) then
|
|
local ok,err = CanLuaDev(pl,strcmd,command,nil,nil,nil)
|
|
if not ok then
|
|
return RejectCommand (pl,err or command)
|
|
end
|
|
id = GetPlayerIdentifier(pl,str) or pl
|
|
else
|
|
pl = "Console"
|
|
id = pl
|
|
end
|
|
func(pl,cmds,strcmd,id)
|
|
end)
|
|
else
|
|
concommand.Add('lua_'..str,function(_,_,cmds,strcmd)
|
|
func(pl,cmds,strcmd,str)
|
|
end,(!complete and function(...) return AutoComplete(str,...) end) or nil)
|
|
end
|
|
end
|