easylua = {} local s = easylua local _R = debug.getregistry() 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 -- Rate limiting, still bad local spams=setmetatable({},{__mode='k'}) local function canspam(pl,len) local now = RealTime() local nextspam = spams[pl] or 0 if now>nextspam then nextspam = now + len>100 and 3 or 1 spams[pl] = nextspam return true else local plstr = tostring(pl) timer.Create("easylua_pspam",5,1,function() Msg "[Easylua Print] Lost messages due to spam: " print(plstr) end) end end function easylua.CMDPrint(ply, cmd, args, fulln) if not canspam(ply,#fulln) then return end 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) local str = net.ReadString() if not canspam(ply,#str) then return end 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 == "#afk" then return afk end if str == "#us" then return us end if str == "#them" then return them end if str == "#friends" then return friends end if str == "#humans" then return humans end if str == "#bots" then return bots 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 ipairs(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 ipairs(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 and str[1] == "7" then local ply = player.GetBySteamID64(str) if ply and ply:IsValid() then return ply end end -- ip if SERVER then if str:find("%d+%.%d+%.%d+%.%d+") then for key, _ply in ipairs(player.GetAll()) do if _ply:IPAddress():find(str) then return _ply end end end end -- search in sensible order -- search exact for _,ply in ipairs(player.GetAll()) do if ply:Nick()==str then return ply end end -- Search bots so we target those first for key, ply in ipairs(player.GetBots()) do if comparenick(ply:Nick(), str) then return ply end end -- search from beginning of nick for _,ply in ipairs(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 ipairs(player.GetAll()) do if comparenick(ply:Nick(), str) then return ply end if _G.UndecorateNick and comparenick( UndecorateNick( ply:Nick() ), str) then return ply end end -- search RealName if _R.Player.RealName then for _, ply in ipairs(player.GetAll()) do if comparenick(ply:RealNick(), str) then return ply end end end if IsValid(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 ipairs(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 else for key, ent in ipairs(ents.GetAll()) do if compareentity(ent, str) then return ent end 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 ipairs(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 ipairs(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 ipairs(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] = rawget(_G, k) rawset(_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 rawset(_G, key, s.oldvars[key]) else rawset(_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 ipairs(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 ipairs(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 include("tinylua.lua") -- Force it to load before we use it local next = next local type = type local INTERNAL = {} local META = {} function META:__call() error("Undefined __call") end function META:__index(key) local wrapped = tinylua(self()) return wrapped[key] end function META:__newindex(key, value) local wrapped = tinylua(self()) wrapped[key] = value end function CreateAllFunction(filter, inputTbl) local allMeta = {} for ind, metamethod in pairs(META)do allMeta[ind] = metamethod end function allMeta:__call() return (isfunction(filter) and filter() or filter) end return setmetatable(inputTbl or {}, allMeta) end function INTERNAL:get() return self() end all = CreateAllFunction(player.GetAll) humans = CreateAllFunction(player.GetHumans) bots = CreateAllFunction(player.GetBots) afk = CreateAllFunction(function() local t = {} for k,v in ipairs(player.GetAll()) do if not v.IsAFK then break end if v:IsAFK() then t[#t+1] = v end end return t end) 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 ipairs(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) npcs = CreateAllFunction(function() local t = {} for _, ent in pairs(ents.GetAll())do if ent:IsNPC() then table.insert(t, ent) end end return t end) allof = function(class) if isentity(class) and IsValid(class) then class = class:GetClass() end local results = ents.FindByClass(class) return CreateAllFunction(function() return results end, results) end props = CreateAllFunction(function() return ents.FindByClass("prop_physics") end) if SERVER then these = CreateAllFunction(function() return constraint.GetAllConstrainedEntities(_G.this) end) end those = CreateAllFunction(function() return ents.FindInSphere(_G.there, 250) end) end