745 lines
15 KiB
Lua
745 lines
15 KiB
Lua
easylua = {} local s = easylua
|
|
|
|
local function compare(a, b)
|
|
|
|
if a == b then return true end
|
|
if a:find(b, nil, true) then return true end
|
|
if a:lower() == b:lower() then return true end
|
|
if a:lower():find(b:lower(), nil, true) then return true end
|
|
|
|
return false
|
|
end
|
|
|
|
local function comparenick(a, b)
|
|
local MatchTransliteration = GLib and GLib.UTF8 and GLib.UTF8.MatchTransliteration
|
|
if not MatchTransliteration then return compare (a, b) end
|
|
|
|
if a == b then return true end
|
|
if a:lower() == b:lower() then return true end
|
|
if MatchTransliteration(a, b) then return true end
|
|
|
|
return false
|
|
end
|
|
|
|
local function compareentity(ent, str)
|
|
if ent.GetName and compare(ent:GetName(), str) then
|
|
return true
|
|
end
|
|
|
|
if ent:GetModel() and compare(ent:GetModel(), str) then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local TagPrintOnServer = "elpos"
|
|
if CLIENT then
|
|
function easylua.PrintOnServer(...)
|
|
local args = {...}
|
|
local new = {}
|
|
|
|
for key, value in pairs(args) do
|
|
table.insert(new, luadata and luadata.ToString(value) or tostring(value))
|
|
end
|
|
net.Start(TagPrintOnServer)
|
|
local str = table.concat(new," ")
|
|
local max = 256
|
|
net.WriteString(str:sub(1,max))
|
|
net.WriteBool(#str>max)
|
|
net.SendToServer()
|
|
end
|
|
else
|
|
util.AddNetworkString(TagPrintOnServer)
|
|
end
|
|
|
|
function easylua.Print(...)
|
|
if CLIENT then
|
|
easylua.PrintOnServer(...)
|
|
end
|
|
if SERVER then
|
|
local args = {...}
|
|
local str = ""
|
|
|
|
Msg(string.format("[ELua %s] ", IsValid(me) and me:Nick() or "Sv"))
|
|
|
|
for key, value in pairs(args) do
|
|
str = str .. type(value) == "string" and value or luadata.ToString(value) or tostring(value)
|
|
|
|
if key ~= #args then
|
|
str = str .. ","
|
|
end
|
|
end
|
|
|
|
print(str)
|
|
end
|
|
end
|
|
|
|
if SERVER then
|
|
function easylua.CMDPrint(ply, cmd, args)
|
|
args = table.concat(args, ", ")
|
|
|
|
Msg(string.format("[ELua %s] ", IsValid(ply) and ply:Nick() or "Sv"))
|
|
print(args)
|
|
end
|
|
-- concommand.Add("easylua_print", easylua.CMDPrint)
|
|
|
|
-- net.Receive(TagPrintOnServer,function(len,ply)
|
|
-- if not luadev.CanLuaDev(ply) then return end
|
|
|
|
-- local str = net.ReadString()
|
|
-- str=str:sub(1,512)
|
|
-- local more = net.ReadBool()
|
|
-- Msg(string.format("[ELua %s] ", IsValid(ply) and ply:Nick() or "Sv"))
|
|
-- local outstr = ('%s%s'):format(str,more and "..." or ""):gsub("[\r\n]"," ")
|
|
-- print(outstr)
|
|
-- end)
|
|
end
|
|
|
|
function easylua.FindEntity(str)
|
|
if not str then return NULL end
|
|
|
|
str = tostring(str)
|
|
|
|
if str == "#this" and IsEntity(this) and this:IsValid() then
|
|
return this
|
|
end
|
|
|
|
if str == "#owner" and IsEntity(this) and this:IsValid() then
|
|
local owner = this.CPPIGetOwner and this:CPPIGetOwner() or this:GetOwner()
|
|
return owner
|
|
end
|
|
|
|
if str == "#me" and IsEntity(me) and me:IsPlayer() then
|
|
return me
|
|
end
|
|
|
|
if str == "#all" then
|
|
return all
|
|
end
|
|
|
|
if str == "#us" then
|
|
return us
|
|
end
|
|
|
|
if str == "#them" then
|
|
return them
|
|
end
|
|
|
|
if str == "#friends" then
|
|
return friends
|
|
end
|
|
|
|
if str == "#randply" then
|
|
return table.Random(player.GetAll())
|
|
end
|
|
|
|
if str:sub(1,1) == "#" then
|
|
local str = str:sub(2)
|
|
|
|
if #str > 0 then
|
|
str = str:lower()
|
|
local found
|
|
|
|
for teamid, data in pairs(team.GetAllTeams()) do
|
|
if data.Name:lower() == str then
|
|
found = teamid
|
|
break
|
|
end
|
|
end
|
|
if found then
|
|
return CreateAllFunction(function()
|
|
local t = {}
|
|
for k,v in next,player.GetAll() do
|
|
if v:IsPlayer() and v:Team() == found then
|
|
t[#t+1] = v
|
|
end
|
|
end
|
|
return t
|
|
end)
|
|
end
|
|
|
|
|
|
for key, ent in pairs(ents.GetAll()) do
|
|
if ent:GetClass():lower() == str then
|
|
found = str
|
|
break
|
|
end
|
|
end
|
|
if found then
|
|
return CreateAllFunction(function()
|
|
return ents.FindByClass(found)
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- unique id
|
|
local ply = player.GetByUniqueID(str)
|
|
if ply and ply:IsPlayer() then
|
|
return ply
|
|
end
|
|
|
|
-- steam id
|
|
if str:find("STEAM") then
|
|
for key, _ply in pairs(player.GetAll()) do
|
|
if _ply:SteamID() == str then
|
|
return _ply
|
|
end
|
|
end
|
|
end
|
|
|
|
if str:sub(1,1) == "_" and tonumber(str:sub(2)) then
|
|
str = str:sub(2)
|
|
end
|
|
|
|
if tonumber(str) then
|
|
ply = Entity(tonumber(str))
|
|
if ply:IsValid() then
|
|
return ply
|
|
end
|
|
end
|
|
|
|
-- community id
|
|
if #str == 17 then
|
|
|
|
end
|
|
|
|
-- ip
|
|
if SERVER then
|
|
if str:find("%d+%.%d+%.%d+%.%d+") then
|
|
for key, _ply in pairs(player.GetAll()) do
|
|
if _ply:IPAddress():find(str) then
|
|
return _ply
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- search in sensible order
|
|
|
|
-- search exact
|
|
for _,ply in pairs(player.GetAll()) do
|
|
if ply:Nick()==str then
|
|
return ply
|
|
end
|
|
end
|
|
|
|
-- Search bots so we target those first
|
|
for key, ply in pairs(player.GetBots()) do
|
|
if comparenick(ply:Nick(), str) then
|
|
return ply
|
|
end
|
|
end
|
|
|
|
-- search from beginning of nick
|
|
for _,ply in pairs(player.GetHumans()) do
|
|
if ply:Nick():lower():find(str,1,true)==1 then
|
|
return ply
|
|
end
|
|
end
|
|
|
|
-- Search normally and search with colorcode stripped
|
|
for key, ply in pairs(player.GetAll()) do
|
|
if comparenick(ply:Nick(), str) then
|
|
return ply
|
|
end
|
|
if comparenick(ply:Nick():gsub("%^%d+", ""), str) then
|
|
return ply
|
|
end
|
|
end
|
|
|
|
if not me or not isentity(me) or not me:IsPlayer() then
|
|
for key, ent in pairs(ents.GetAll()) do
|
|
if compareentity(ent, str) then
|
|
return ent
|
|
end
|
|
end
|
|
else
|
|
local tr = me:GetEyeTrace()
|
|
local plpos = tr and tr.HitPos or me:GetPos()
|
|
local closest,mind = nil,math.huge
|
|
for key, ent in pairs(ents.GetAll()) do
|
|
local d = ent:GetPos():DistToSqr(plpos)
|
|
if d < mind and compareentity(ent, str) then
|
|
closest = ent
|
|
mind = d
|
|
end
|
|
end
|
|
if closest then
|
|
return closest
|
|
end
|
|
end
|
|
|
|
do -- class
|
|
|
|
local _str, idx = str:match("(.-)(%d+)$")
|
|
if idx then
|
|
idx = tonumber(idx)
|
|
str = _str
|
|
else
|
|
str = str
|
|
idx = (me and me.easylua_iterator) or 0
|
|
|
|
if me and isentity(me) and me:IsPlayer() then
|
|
|
|
local tr = me:GetEyeTrace()
|
|
local plpos = tr and tr.HitPos or me:GetPos()
|
|
local closest,mind = nil,math.huge
|
|
for key, ent in pairs(ents.GetAll()) do
|
|
local d = ent:GetPos():DistToSqr(plpos)
|
|
if d < mind and compare(ent:GetClass(), str) then
|
|
closest = ent
|
|
mind = d
|
|
end
|
|
end
|
|
if closest then
|
|
return closest
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
local found = {}
|
|
|
|
for key, ent in pairs(ents.GetAll()) do
|
|
if compare(ent:GetClass(), str) then
|
|
table.insert(found, ent)
|
|
end
|
|
end
|
|
|
|
return found[math.Clamp(idx%#found, 1, #found)] or NULL
|
|
end
|
|
end
|
|
|
|
function easylua.CreateEntity(class, callback)
|
|
local mdl = "error.mdl"
|
|
|
|
if IsEntity(class) and class:IsValid() then
|
|
this = class
|
|
elseif class:find(".mdl", nil, true) then
|
|
mdl = class
|
|
class = "prop_physics"
|
|
|
|
this = ents.Create(class)
|
|
this:SetModel(mdl)
|
|
else
|
|
this = ents.Create(class)
|
|
end
|
|
|
|
if callback and type(callback) == 'function' then
|
|
callback(this);
|
|
end
|
|
|
|
this:Spawn()
|
|
this:SetPos(there + Vector(0,0,this:BoundingRadius() * 2))
|
|
this:DropToFloor()
|
|
this:PhysWake()
|
|
|
|
undo.Create(class)
|
|
undo.SetPlayer(me)
|
|
undo.AddEntity(this)
|
|
undo.Finish()
|
|
|
|
me:AddCleanup("props", this)
|
|
|
|
return this
|
|
end
|
|
|
|
function easylua.CopyToClipboard(var, ply)
|
|
ply = ply or me
|
|
if luadata then
|
|
local str = luadata.ToString(var)
|
|
|
|
if not str and IsEntity(var) and var:IsValid() then
|
|
if var:IsPlayer() then
|
|
str = string.format("player.GetByUniqueID(--[[%s]] %q)", var:GetName(), var:UniqueID())
|
|
else
|
|
str = string.format("Entity(%i)", var:EntIndex())
|
|
end
|
|
|
|
end
|
|
|
|
if CLIENT then
|
|
SetClipboardText(str)
|
|
end
|
|
|
|
if SERVER then
|
|
local str = string.format("SetClipboardText(%q)", str)
|
|
if #str > 255 then
|
|
if luadev and luadev.RunOnClient then
|
|
luadev.RunOnClient(str, ply)
|
|
else
|
|
error("Text too long to send and luadev not found",1)
|
|
end
|
|
else
|
|
ply:SendLua(str)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local started = false
|
|
function easylua.Start(ply)
|
|
if started then
|
|
Msg"[ELua] "print("Session not ended for ",_G.me or (s.vars and s.vars.me),", restarting session for",ply)
|
|
easylua.End()
|
|
end
|
|
started = true
|
|
|
|
ply = ply or CLIENT and LocalPlayer() or nil
|
|
|
|
if not ply or not IsValid(ply) then return end
|
|
|
|
local vars = {}
|
|
local trace = util.QuickTrace(ply:EyePos(), ply:GetAimVector() * 10000, {ply, ply:GetVehicle()})
|
|
|
|
if trace.Entity:IsWorld() then
|
|
trace.Entity = NULL
|
|
end
|
|
|
|
vars.me = ply
|
|
vars.this = trace.Entity
|
|
vars.wep = ply:GetActiveWeapon()
|
|
vars.veh = ply:GetVehicle()
|
|
|
|
vars.we = {}
|
|
|
|
for k, v in pairs(ents.FindInSphere(ply:GetPos(), 512)) do
|
|
if v:IsPlayer() then
|
|
table.insert(vars.we, v)
|
|
end
|
|
end
|
|
|
|
vars.there = trace.HitPos
|
|
vars.here = trace.StartPos
|
|
vars.dir = ply:GetAimVector()
|
|
|
|
vars.trace = trace
|
|
vars.length = trace.StartPos:Distance(trace.HitPos)
|
|
|
|
vars.copy = s.CopyToClipboard
|
|
vars.create = s.CreateEntity
|
|
vars.prints = s.PrintOnServer
|
|
|
|
if vars.this:IsValid() then
|
|
vars.phys = vars.this:GetPhysicsObject()
|
|
vars.model = vars.this:GetModel()
|
|
end
|
|
|
|
vars.E = s.FindEntity
|
|
vars.last = ply.easylua_lastvars
|
|
|
|
|
|
s.vars = vars
|
|
local old_G={}
|
|
s.oldvars=old_G
|
|
|
|
for k,v in pairs(vars) do old_G[k]=_G[k] _G[k] = v end
|
|
|
|
-- let this gc. maybe allow few more recursions.
|
|
if vars.last and istable(vars.last) then vars.last.last = nil end
|
|
|
|
ply.easylua_lastvars = vars
|
|
ply.easylua_iterator = (ply.easylua_iterator or 0) + 1
|
|
end
|
|
|
|
function easylua.End()
|
|
if not started then
|
|
Msg"[ELua] "print"Ending session without starting"
|
|
end
|
|
started = false
|
|
|
|
if s.vars then
|
|
for key, value in pairs(s.vars) do
|
|
if s.oldvars and s.oldvars[key] then
|
|
_G[key] = s.oldvars[key]
|
|
else
|
|
_G[key] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
do -- env meta
|
|
local META = {}
|
|
|
|
local _G = _G
|
|
local easylua = easylua
|
|
local tonumber = tonumber
|
|
|
|
local nils={
|
|
["CLIENT"]=true,
|
|
["SERVER"]=true,
|
|
}
|
|
function META:__index(key)
|
|
local var = _G[key]
|
|
|
|
if var ~= nil then
|
|
return var
|
|
end
|
|
|
|
if not nils [key] then -- uh oh
|
|
var = easylua.FindEntity(key)
|
|
if var:IsValid() then
|
|
return var
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function META:__newindex(key, value)
|
|
_G[key] = value
|
|
end
|
|
|
|
easylua.EnvMeta = setmetatable({}, META)
|
|
end
|
|
|
|
function easylua.RunLua(ply, code, env_name)
|
|
local data =
|
|
{
|
|
error = false,
|
|
args = {},
|
|
}
|
|
|
|
easylua.Start(ply)
|
|
if s.vars then
|
|
local header = ""
|
|
|
|
for key, value in next,(s.vars or {}) do
|
|
header = header .. string.format("local %s = %s ", key, key)
|
|
end
|
|
|
|
code = header .. "; " .. code
|
|
end
|
|
|
|
env_name = env_name or string.format("%s", tostring(
|
|
IsValid(ply) and ply:IsPlayer()
|
|
and "["..ply:SteamID():gsub("STEAM_","").."]"..ply:Name()
|
|
or ply))
|
|
|
|
data.env_name = env_name
|
|
|
|
local func = CompileString(code, env_name, false)
|
|
|
|
if type(func) == "function" then
|
|
setfenv(func, easylua.EnvMeta)
|
|
|
|
local args = {pcall(func)}
|
|
|
|
if args[1] == false then
|
|
data.error = args[2]
|
|
end
|
|
|
|
table.remove(args, 1)
|
|
data.args = args
|
|
else
|
|
data.error = func
|
|
end
|
|
easylua.End()
|
|
|
|
return data
|
|
end
|
|
|
|
-- legacy luadev compatibility
|
|
|
|
local STAGE_PREPROCESS=1
|
|
local STAGE_COMPILED=2
|
|
local STAGE_POST=3
|
|
|
|
local insession = false
|
|
hook.Add("LuaDevProcess","easylua",function(stage,script,info,extra,func)
|
|
if stage==STAGE_PREPROCESS then
|
|
|
|
if insession then
|
|
insession=false
|
|
easylua.End()
|
|
end
|
|
|
|
if not istable(extra) or not IsValid(extra.ply) or not script or extra.easylua==false then
|
|
return
|
|
end
|
|
|
|
insession = true
|
|
easylua.Start(extra.ply)
|
|
|
|
local t={}
|
|
for key, value in pairs(easylua.vars or {}) do
|
|
t[#t+1]=key
|
|
end
|
|
if #t>0 then
|
|
script=' local '..table.concat(t,", ")..' = '..table.concat(t,", ")..' ; '..script
|
|
end
|
|
|
|
--ErrorNoHalt(script)
|
|
return script
|
|
|
|
elseif stage==STAGE_COMPILED then
|
|
|
|
if not istable(extra) or not IsValid(extra.ply) or not isfunction(func) or extra.easylua==false then
|
|
if insession then
|
|
insession=false
|
|
easylua.End()
|
|
end
|
|
return
|
|
end
|
|
|
|
if insession then
|
|
local env = getfenv(func)
|
|
if not env or env==_G then
|
|
setfenv(func, easylua.EnvMeta)
|
|
end
|
|
end
|
|
|
|
elseif stage == STAGE_POST and insession then
|
|
insession=false
|
|
easylua.End()
|
|
end
|
|
end)
|
|
|
|
function easylua.StartWeapon(classname)
|
|
_G.SWEP = {
|
|
Primary = {},
|
|
Secondary = {},
|
|
ViewModelFlip = false,
|
|
}
|
|
|
|
SWEP.Base = "weapon_base"
|
|
|
|
SWEP.ClassName = classname
|
|
end
|
|
|
|
function easylua.EndWeapon(spawn, reinit)
|
|
if not SWEP then error"missing SWEP" end
|
|
if not SWEP.ClassName then error"missing classname" end
|
|
|
|
weapons.Register(SWEP, SWEP.ClassName)
|
|
|
|
for key, entity in pairs(ents.FindByClass(SWEP.ClassName)) do
|
|
--if entity:GetTable() then table.Merge(entity:GetTable(), SWEP) end
|
|
if reinit then
|
|
entity:Initialize()
|
|
end
|
|
end
|
|
|
|
if SERVER and spawn then
|
|
SafeRemoveEntity(me:GetWeapon(SWEP.ClassName))
|
|
local me = me
|
|
local class = SWEP.ClassName
|
|
timer.Simple(0.2, function() if me:IsPlayer() then me:Give(class) end end)
|
|
end
|
|
|
|
SWEP = nil
|
|
end
|
|
|
|
function easylua.StartEntity(classname)
|
|
_G.ENT = {}
|
|
|
|
ENT.ClassName = classname or "no_ent_name_" .. me:Nick() .. "_" .. me:UniqueID()
|
|
end
|
|
|
|
function easylua.EndEntity(spawn, reinit)
|
|
|
|
ENT.Model = ENT.Model or Model("models/props_borealis/bluebarrel001.mdl")
|
|
|
|
if not ENT.Base then -- there can be Base without Type but no Type without base without redefining every function so um
|
|
ENT.Base = "base_anim"
|
|
ENT.Type = ENT.Type or "anim"
|
|
end
|
|
|
|
scripted_ents.Register(ENT, ENT.ClassName)
|
|
|
|
for key, entity in pairs(ents.FindByClass(ENT.ClassName)) do
|
|
--table.Merge(entity:GetTable(), ENT)
|
|
if reinit then
|
|
entity:Initialize()
|
|
end
|
|
end
|
|
|
|
if SERVER and spawn then
|
|
create(ENT.ClassName)
|
|
end
|
|
|
|
ENT = nil
|
|
end
|
|
|
|
do -- all
|
|
local next = next
|
|
local type = type
|
|
local rawget = rawget
|
|
|
|
local META = {}
|
|
|
|
function META:__call()
|
|
return rawget(self, 'get')()
|
|
end
|
|
|
|
function META:__index(key)
|
|
if type(key) == 'number' then
|
|
return rawget(self, 'get')()[key]
|
|
end
|
|
|
|
return function(_, ...)
|
|
local args = {}
|
|
|
|
for _, ent in next, rawget(self, 'get')() do
|
|
if type(ent[key]) == "function" or ent[key] == "table" and type(ent[key].__call) == "function" and getmetatable(ent[key]) then
|
|
local rets = {ent[key](ent, ...)}
|
|
if select('#', unpack(rets)) > 1 then
|
|
args[ent] = {rets}
|
|
else
|
|
args[ent] = rets[1]
|
|
end
|
|
else
|
|
ErrorNoHalt("attempt to call field '" .. key .. "' on ".. tostring(ent) .." a " .. type(ent[key]) .. " value\n")
|
|
end
|
|
end
|
|
|
|
return args
|
|
end
|
|
end
|
|
|
|
function META:__newindex(key, value)
|
|
if type(key) == 'number' then error'setting number index on entity' end
|
|
for _, ent in next, rawget(self, 'get')() do
|
|
ent[key] = value
|
|
end
|
|
end
|
|
|
|
|
|
function CreateAllFunction(filter)
|
|
return setmetatable({
|
|
get = filter,
|
|
}, META)
|
|
end
|
|
|
|
all = CreateAllFunction(player.GetAll)
|
|
humans = CreateAllFunction(player.GetHumans)
|
|
bots = CreateAllFunction(player.GetBots)
|
|
us = CreateAllFunction(function()
|
|
if _G.we then return _G.we end
|
|
if _G.me then return {_G.me} end
|
|
return {}
|
|
end)
|
|
them = CreateAllFunction(function()
|
|
local me = _G.me
|
|
local we = _G.we or {}
|
|
table.RemoveByValue(we, me)
|
|
return we
|
|
end)
|
|
friends = CreateAllFunction(function()
|
|
local me = _G.me
|
|
local t = {}
|
|
for k,v in next,player.GetHumans() do
|
|
if v == me then continue end
|
|
if (me.IsFriend and me:IsFriend(v) or (CLIENT and v:GetFriendStatus() == 'friend')) then
|
|
t[#t+1] = v
|
|
end
|
|
end
|
|
return t
|
|
end)
|
|
|
|
props = CreateAllFunction(function() return ents.FindByClass'prop_physics' end)
|
|
these = CreateAllFunction(function() return constraint.GetAllConstrainedEntities(_G.this) end)
|
|
end
|