This commit is contained in:
lifestorm
2024-08-04 22:55:00 +03:00
parent 8064ba84d8
commit 73479cff9e
7338 changed files with 1718883 additions and 14 deletions

View File

@@ -0,0 +1,674 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local animations = pac.animations or {}
animations.playing = {}
animations.playing = animations.playing or {}
animations.registered = animations.registered or {}
do
local old_types = {
[0] = "gesture", -- Gestures are keyframed animations that use the current position and angles of the bones. They play once and then stop automatically.
[1] = "posture", -- Postures are static animations that use the current position and angles of the bones. They stay that way until manually stopped. Use TimeToArrive if you want to have a posture lerp.
[2] = "stance", -- Stances are keyframed animations that use the current position and angles of the bones. They play forever until manually stopped. Use RestartFrame to specify a frame to go to if the animation ends (instead of frame 1).
[3] = "sequence", -- Sequences are keyframed animations that use the reference pose. They play forever until manually stopped. Use RestartFrame to specify a frame to go to if the animation ends (instead of frame 1).
}
local old_interpolations = {
[0] = "linear", -- Straight linear interp.
[1] = "cosine", -- Best compatability / quality balance.
[1] = "cubic", -- Overall best quality blending but may cause animation frames to go 'over the top'.
}
function animations.ConvertOldData(data)
if tonumber(data.Type) then
data.Type = tonumber(data.Type)
end
if tonumber(data.Interpolation) then
data.Interpolation = tonumber(data.Interpolation)
end
if isnumber(data.Type) then
data.Type = old_types[data.Type]
end
if isnumber(data.Interpolation) then
data.Interpolation = old_interpolations[data.Interpolation]
end
data.Type = data.Type or "sequence"
data.Interpolation = data.Interpolation or "cosine"
end
end
animations.eases = {}
local eases = animations.eases
do
local c1 = 1.70158
local c3 = c1 + 1
local c2 = c1 * 1.525
local c4 = ( 2 * math.pi ) / 3
local c5 = ( 2 * math.pi ) / 4.5
local n1 = 7.5625
local d1 = 2.75
local pi = math.pi
local cos = math.cos
local sin = math.sin
local sqrt = math.sqrt
eases.InSine = function(x)
return 1 - cos( ( x * pi ) / 2 )
end
eases.OutSine = function(x)
return sin( ( x * pi ) / 2 )
end
eases.InOutSine = function(x)
return -( cos( pi * x ) - 1 ) / 2
end
eases.InQuad = function(x)
return x ^ 2
end
eases.OutQuad = function(x)
return 1 - ( 1 - x ) * ( 1 - x )
end
eases.InOutQuad = function(x)
return x < 0.5 && 2 * x ^ 2 || 1 - ( ( -2 * x + 2 ) ^ 2 ) / 2
end
eases.InCubic = function(x)
return x ^ 3
end
eases.OutCubic = function(x)
return 1 - ( ( 1 - x ) ^ 3 )
end
eases.InOutCubic = function(x)
return x < 0.5 && 4 * x ^ 3 || 1 - ( ( -2 * x + 2 ) ^ 3 ) / 2
end
eases.InQuart = function(x)
return x ^ 4
end
eases.OutQuart = function(x)
return 1 - ( ( 1 - x ) ^ 4 )
end
eases.InOutQuart = function(x)
return x < 0.5 && 8 * x ^ 4 || 1 - ( ( -2 * x + 2 ) ^ 4 ) / 2
end
eases.InQuint = function(x)
return x ^ 5
end
eases.OutQuint = function(x)
return 1 - ( ( 1 - x ) ^ 5 )
end
eases.InOutQuint = function(x)
return x < 0.5 && 16 * x ^ 5 || 1 - ( ( -2 * x + 2 ) ^ 5 ) / 2
end
eases.InExpo = function(x)
return x == 0 && 0 || ( 2 ^ ( 10 * x - 10 ) )
end
eases.OutExpo = function(x)
return x == 1 && 1 || 1 - ( 2 ^ ( -10 * x ) )
end
eases.InOutExpo = function(x)
return x == 0
&& 0
|| x == 1
&& 1
|| x < 0.5 && ( 2 ^ ( 20 * x - 10 ) ) / 2
|| ( 2 - ( 2 ^ ( -20 * x + 10 ) ) ) / 2
end
eases.InCirc = function(x)
return 1 - sqrt( 1 - ( x ^ 2 ) )
end
eases.OutCirc = function(x)
return sqrt( 1 - ( ( x - 1 ) ^ 2 ) )
end
eases.InOutCirc = function(x)
return x < 0.5
&& ( 1 - sqrt( 1 - ( ( 2 * x ) ^ 2 ) ) ) / 2
|| ( sqrt( 1 - ( ( -2 * x + 2 ) ^ 2 ) ) + 1 ) / 2
end
eases.InBack = function(x)
return c3 * x ^ 3 - c1 * x ^ 2
end
eases.OutBack = function(x)
return 1 + c3 * ( ( x - 1 ) ^ 3 ) + c1 * ( ( x - 1 ) ^ 2 )
end
eases.InOutBack = function(x)
return x < 0.5
&& ( ( ( 2 * x ) ^ 2 ) * ( ( c2 + 1 ) * 2 * x - c2 ) ) / 2
|| ( ( ( 2 * x - 2 ) ^ 2 ) * ( ( c2 + 1 ) * ( x * 2 - 2 ) + c2 ) + 2 ) / 2
end
eases.InElastic = function(x)
return x == 0
&& 0
|| x == 1
&& 1
|| -( 2 ^ ( 10 * x - 10 ) ) * sin( ( x * 10 - 10.75 ) * c4 )
end
eases.OutElastic = function(x)
return x == 0
&& 0
|| x == 1
&& 1
|| ( 2 ^ ( -10 * x ) ) * sin( ( x * 10 - 0.75 ) * c4 ) + 1
end
eases.InOutElastic = function(x)
return x == 0
&& 0
|| x == 1
&& 1
|| x < 0.5
&& -( ( 2 ^ ( 20 * x - 10 ) ) * sin( ( 20 * x - 11.125 ) * c5 ) ) / 2
|| ( ( 2 ^ ( -20 * x + 10 ) ) * sin( ( 20 * x - 11.125 ) * c5 ) ) / 2 + 1
end
eases.InBounce = function(x)
return 1 - eases.OutBounce( 1 - x )
end
eases.OutBounce = function(x)
if ( x < 1 / d1 ) then
return n1 * x ^ 2
elseif ( x < 2 / d1 ) then
x = x - ( 1.5 / d1 )
return n1 * x ^ 2 + 0.75
elseif ( x < 2.5 / d1 ) then
x = x - ( 2.25 / d1 )
return n1 * x ^ 2 + 0.9375
else
x = x - ( 2.625 / d1 )
return n1 * x ^ 2 + 0.984375
end
end
eases.InOutBounce = function(x)
return x < 0.5
&& ( 1 - eases.OutBounce( 1 - 2 * x ) ) / 2
|| ( 1 + eases.OutBounce( 2 * x - 1 ) ) / 2
end
end
function animations.GetRegisteredAnimations()
return animations.registered
end
function animations.RegisterAnimation(name, tInfo)
if tInfo and tInfo.FrameData then
local BonesUsed = {}
for _, tFrame in ipairs(tInfo.FrameData) do
for iBoneID, tBoneTable in pairs(tFrame.BoneInfo) do
BonesUsed[iBoneID] = (BonesUsed[iBoneID] or 0) + 1
tBoneTable.MU = tBoneTable.MU or 0
tBoneTable.MF = tBoneTable.MF or 0
tBoneTable.MR = tBoneTable.MR or 0
tBoneTable.RU = tBoneTable.RU or 0
tBoneTable.RF = tBoneTable.RF or 0
tBoneTable.RR = tBoneTable.RR or 0
end
end
if #tInfo.FrameData > 1 then
for iBoneUsed in pairs(BonesUsed) do
for _, tFrame in ipairs(tInfo.FrameData) do
if not tFrame.BoneInfo[iBoneUsed] then
tFrame.BoneInfo[iBoneUsed] = {MU = 0, MF = 0, MR = 0, RU = 0, RF = 0, RR = 0}
end
end
end
end
end
animations.registered[name] = tInfo
do return end
for _, ent in ipairs(animations.playing) do
if ent.pac_animations and ent.pac_animations[name] then
local frame, delta = animations.GetEntityAnimationFrame(ent, name)
animations.ResetEntityAnimation(ent, name)
animations.SetEntityAnimationFrame(ent, name, frame, delta)
end
end
end
local function AdvanceFrame(tGestureTable, tFrameData)
if tGestureTable.Paused then return end
if tGestureTable.TimeScale == 0 then
local max = #tGestureTable.FrameData
local offset = tGestureTable.Offset
local start = tGestureTable.RestartFrame or 1
offset = Lerp(offset%1, start, max + 1)
tGestureTable.Frame = math.floor(offset)
tGestureTable.FrameDelta = offset%1
return true
end
tGestureTable.FrameDelta = tGestureTable.FrameDelta + FrameTime() * tFrameData.FrameRate * tGestureTable.TimeScale
if tGestureTable.FrameDelta > 1 then
tGestureTable.Frame = tGestureTable.Frame + 1
tGestureTable.FrameDelta = math.min(1, tGestureTable.FrameDelta - 1)
if tGestureTable.Frame > #tGestureTable.FrameData then
tGestureTable.Frame = math.min(tGestureTable.RestartFrame or 1, #tGestureTable.FrameData)
return true
end
end
return false
end
local function CosineInterpolation(y1, y2, mu)
local mu2 = (1 - math.cos(mu * math.pi)) / 2
return y1 * (1 - mu2) + y2 * mu2
end
local function CubicInterpolation(y0, y1, y2, y3, mu)
local mu2 = mu * mu
local a0 = y3 - y2 - y0 + y1
return a0 * mu * mu2 + (y0 - y1 - a0) * mu2 + (y2 - y0) * mu + y1
end
local EMPTYBONEINFO = {MU = 0, MR = 0, MF = 0, RU = 0, RR = 0, RF = 0}
local function GetFrameBoneInfo(ent, tGestureTable, iFrame, iBoneID)
local tPrev = tGestureTable.FrameData[iFrame]
if tPrev then
return tPrev.BoneInfo[iBoneID] or tPrev.BoneInfo[ent:GetBoneName(iBoneID)] or EMPTYBONEINFO
end
return EMPTYBONEINFO
end
local function ProcessAnimations(ent)
for name, tbl in pairs(ent.pac_animations) do
local frame = tbl.Frame
local frame_data = tbl.FrameData[frame]
local frame_delta = tbl.FrameDelta
local die_time = tbl.DieTime
local power = tbl.Power
if die_time and die_time - 0.125 <= CurTime() then
power = power * (die_time - CurTime()) / 0.125
end
if die_time and die_time <= CurTime() then
animations.StopEntityAnimation(ent, name)
elseif not tbl.PreCallback or not tbl.PreCallback(ent, name, tbl, frame, frame_data, frame_delta) then
if tbl.ShouldPlay and not tbl.ShouldPlay(ent, name, tbl, frame, frame_data, frame_delta, power) then
animations.StopEntityAnimation(ent, name, 0.2)
end
if tbl.Type == "gesture" then
if AdvanceFrame(tbl, frame_data) then
animations.StopEntityAnimation(ent, name)
end
elseif tbl.Type == "posture" then
if frame_delta < 1 and tbl.TimeToArrive then
frame_delta = math.min(1, frame_delta + FrameTime() * (1 / tbl.TimeToArrive))
tbl.FrameDelta = frame_delta
end
else
AdvanceFrame(tbl, frame_data)
end
end
end
animations.ResetEntityBoneMatrix(ent)
if not ent.pac_animations then return end
local tBuffer = {}
for _, tbl in pairs(ent.pac_animations) do
local iCurFrame = tbl.Frame
local tFrameData = tbl.FrameData[iCurFrame]
local fFrameDelta = tbl.FrameDelta
local fDieTime = tbl.DieTime
local fPower = tbl.Power
if fDieTime and fDieTime - 0.125 <= CurTime() then
fPower = fPower * (fDieTime - CurTime()) / 0.125
end
local fAmount = fPower * fFrameDelta
for iBoneID, tBoneInfo in pairs(tFrameData.BoneInfo) do
if not isnumber(iBoneID) then
iBoneID = ent:LookupBone(iBoneID)
end
if not iBoneID then goto CONTINUE end
if not tBuffer[iBoneID] then tBuffer[iBoneID] = Matrix() end
local mBoneMatrix = tBuffer[iBoneID]
local vCurBonePos, aCurBoneAng = mBoneMatrix:GetTranslation(), mBoneMatrix:GetAngles()
if not tBoneInfo.Callback or not tBoneInfo.Callback(ent, mBoneMatrix, iBoneID, vCurBonePos, aCurBoneAng, fFrameDelta, fPower) then
local vUp = aCurBoneAng:Up()
local vRight = aCurBoneAng:Right()
local vForward = aCurBoneAng:Forward()
local iInterp = tbl.Interpolation
if iInterp == "linear" then
if tbl.Type == "posture" then
mBoneMatrix:Translate((tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward) * fAmount)
mBoneMatrix:Rotate(Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF) * fAmount)
else
local bi1 = GetFrameBoneInfo(ent, tbl, iCurFrame - 1, iBoneID)
if tFrameData["EaseStyle"] then
local curease = tFrameData["EaseStyle"]
mBoneMatrix:Translate(
LerpVector(
eases[curease](fFrameDelta),
bi1.MU * vUp + bi1.MR * vRight + bi1.MF * vForward,
tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward
) * fPower
)
mBoneMatrix:Rotate(
LerpAngle(
eases[curease](fFrameDelta),
Angle(bi1.RR, bi1.RU, bi1.RF),
Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF)
) * fPower
)
else
mBoneMatrix:Translate(
LerpVector(
fFrameDelta,
bi1.MU * vUp + bi1.MR * vRight + bi1.MF * vForward,
tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward
) * fPower
)
mBoneMatrix:Rotate(
LerpAngle(
fFrameDelta,
Angle(bi1.RR, bi1.RU, bi1.RF),
Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF)
) * fPower
)
end
end
elseif iInterp == "cubic" and tbl.FrameData[iCurFrame - 2] and tbl.FrameData[iCurFrame + 1] then
local bi0 = GetFrameBoneInfo(ent, tbl, iCurFrame - 2, iBoneID)
local bi1 = GetFrameBoneInfo(ent, tbl, iCurFrame - 1, iBoneID)
local bi3 = GetFrameBoneInfo(ent, tbl, iCurFrame + 1, iBoneID)
mBoneMatrix:Translate(CosineInterpolation(bi1.MU * vUp + bi1.MR * vRight + bi1.MF * vForward, tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward, fFrameDelta) * fPower)
mBoneMatrix:Rotate(CubicInterpolation(
Angle(bi0.RR, bi0.RU, bi0.RF),
Angle(bi1.RR, bi1.RU, bi1.RF),
Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF),
Angle(bi3.RR, bi3.RU, bi3.RF),
fFrameDelta
) * fPower)
elseif iInterp == "none" then
mBoneMatrix:Translate((tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward))
mBoneMatrix:Rotate(Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF))
else-- Default is Cosine
local bi1 = GetFrameBoneInfo(ent, tbl, iCurFrame - 1, iBoneID)
mBoneMatrix:Translate(CosineInterpolation(bi1.MU * vUp + bi1.MR * vRight + bi1.MF * vForward, tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward, fFrameDelta) * fPower)
mBoneMatrix:Rotate(CosineInterpolation(Angle(bi1.RR, bi1.RU, bi1.RF), Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF), fFrameDelta) * fPower)
end
end
::CONTINUE::
end
end
for iBoneID, mMatrix in pairs(tBuffer) do
pac.SetEntityBoneMatrix(ent, iBoneID, mMatrix)
end
end
function animations.ResetEntityBoneMatrix(ent)
for i=0, ent:GetBoneCount() - 1 do
pac.ResetEntityBoneMatrix(ent, i)
end
end
function animations.ResetEntityAnimation(ent, name, fDieTime, fPower, fTimeScale)
local animtable = animations.registered[name]
if animtable then
ent.pac_animations = ent.pac_animations or {}
local framedelta = 0
if animtable.Type == "posture" and not animtable.TimeToArrive then
framedelta = 1
end
ent.pac_animations[name] = {
Frame = animtable.StartFrame or 1,
Offset = 0,
FrameDelta = framedelta,
FrameData = animtable.FrameData,
TimeScale = fTimeScale or animtable.TimeScale or 1,
Type = animtable.Type,
RestartFrame = animtable.RestartFrame,
TimeToArrive = animtable.TimeToArrive,
ShouldPlay = animtable.ShouldPlay,
Power = fPower or animtable.Power or 1,
DieTime = fDieTime or animtable.DieTime,
Group = animtable.Group,
UseReferencePose = animtable.UseReferencePose,
Interpolation = animtable.Interpolation,
}
animations.ResetEntityAnimationProperties(ent)
for i,v in ipairs(animations.playing) do
if v == ent then
table.remove(animations.playing, i)
break
end
end
ent:CallOnRemove("pac_animations", function()
for i,v in ipairs(animations.playing) do
if v == ent then
table.remove(animations.playing, i)
break
end
end
end)
table.insert(animations.playing, ent)
end
end
function animations.SetEntityAnimation(ent, name, fDieTime, fPower, fTimeScale)
if ent.pac_animations and ent.pac_animations[name] then return end
animations.ResetEntityAnimation(ent, name, fDieTime, fPower, fTimeScale)
end
function animations.GetEntityAnimation(ent, name)
if ent.pac_animations and ent.pac_animations[name] then return ent.pac_animations[name] end
end
function animations.SetEntityAnimationFrame(ent, name, f, delta)
if ent.pac_animations and ent.pac_animations[name] then
local data = ent.pac_animations[name]
f = math.ceil(f)
f = math.Clamp(f, 1, #data.FrameData)
data.Frame = f
data.FrameDelta = delta and math.Clamp(delta, 0, 1) or 0
end
end
function animations.GetEntityAnimationFrame(ent, name)
if ent.pac_animations and ent.pac_animations[name] then
local data = ent.pac_animations[name]
return data.Frame, data.FrameDelta
end
end
function animations.SetEntityAnimationCycle(ent, name, f)
if ent.pac_animations and ent.pac_animations[name] then
local data = ent.pac_animations[name]
local duration = animations.GetAnimationDuration(ent, name)
f = f%1
f = f * duration
local sec = 0
for i = 1, #data.FrameData do
local dt = (1/data.FrameData[i].FrameRate)
if sec+dt >= f then
data.Frame = i
data.FrameDelta = math.Clamp((f-sec) / dt, 0, 1)
break
end
sec = sec + dt
end
end
end
function animations.GetEntityAnimationCycle(ent, name)
if ent.pac_animations and ent.pac_animations[name] then
local data = ent.pac_animations[name]
local sec = 0
for i = 1, data.Frame - 1 do
local dt = (1/data.FrameData[i].FrameRate)
sec = sec + dt
end
sec = Lerp(data.FrameDelta, sec, sec + (1/data.FrameData[data.Frame].FrameRate))
return sec/animations.GetAnimationDuration(ent, name)
end
end
function animations.GetAnimationDuration(ent, name)
if ent.pac_animations and ent.pac_animations[name] then
local total = 0
for i=1, #ent.pac_animations[name].FrameData do
local v = ent.pac_animations[name].FrameData[i]
total = total+(1/(v.FrameRate or 1))
end
return total
end
return 0
end
local function ResetInSequence(ent)
if ent.pac_animations then
for _, tbl in pairs(ent.pac_animations) do
if tbl.Type == "sequence" and (not tbl.DieTime or CurTime() < tbl.DieTime - 0.125) or tbl.UseReferencePose then
ent.pac_animations_insequence = true
return
end
end
ent.pac_animations_insequence = nil
end
end
pac.AddHook("CalcMainActivity", "animations_reset_sequence", function(ent)
if ent.pac_animations_insequence then
ResetInSequence(ent)
return 0, 0
end
end)
function animations.ResetEntityAnimationProperties(ent)
local anims = ent.pac_animations
if anims and table.Count(anims) > 0 then
ent:SetIK(false)
ResetInSequence(ent)
else
--ent:SetIK(true)
ent.pac_animations = nil
ent.pac_animations_insequence = nil
ent:RemoveCallOnRemove("pac_animations")
for i,v in ipairs(animations.playing) do
if v == ent then
table.remove(animations.playing, i)
end
end
end
end
-- Time is optional, sets the die time to CurTime() + time
function animations.StopEntityAnimation(ent, name, time)
local anims = ent.pac_animations
if anims and anims[name] then
if time then
if anims[name].DieTime then
anims[name].DieTime = math.min(anims[name].DieTime, CurTime() + time)
else
anims[name].DieTime = CurTime() + time
end
else
anims[name] = nil
end
animations.ResetEntityAnimationProperties(ent)
end
end
function animations.StopAllEntityAnimations(ent, time)
if ent.pac_animations then
for name in pairs(ent.pac_animations) do
animations.StopEntityAnimation(ent, name, time)
end
end
end
hook.Add("Think", "pac_custom_animations", function()
for i,v in ipairs(animations.playing) do
if v.pac_animations then
ProcessAnimations(v)
end
end
end)
return animations

View File

@@ -0,0 +1,80 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local lib = {
PI = math.pi,
rand = math.random,
randx = function(a, b)
a = a or -1
b = b or 1
return math.Rand(a, b)
end,
abs = math.abs,
acos = math.acos,
asin = math.asin,
atan = math.atan,
atan2 = math.atan2,
ceil = math.ceil,
cos = math.cos,
cosh = math.cosh,
deg = math.deg,
exp = math.exp,
floor = math.floor,
frexp = math.frexp,
ldexp = math.ldexp,
log = math.log,
log10 = math.log10,
max = math.max,
min = math.min,
rad = math.rad,
sin = math.sin,
sinh = math.sinh,
sqrt = math.sqrt,
tanh = math.tanh,
tan = math.tan,
sgn = function(n) return n>0 and 1 or n<0 and -1 or 0 end,
clamp = math.Clamp,
round = math.Round,
}
local blacklist = {"repeat", "until", "function", "end"}
local function compile_expression(str, extra_lib)
for _, word in pairs(blacklist) do
if str:find("[%p%s]" .. word) or str:find(word .. "[%p%s]") then
return false, string.format("illegal characters used %q", word)
end
end
local functions = {}
for k,v in pairs(lib) do functions[k] = v end
if extra_lib then
for k,v in pairs(extra_lib) do functions[k] = v end
end
functions.select = select
str = "local IN = select(1, ...) return " .. str
local func = CompileString(str, "pac_expression", false)
if isstring(func) then
return false, func
else
setfenv(func, functions)
return true, func
end
end
return compile_expression

View File

@@ -0,0 +1,228 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--TODO: Remake from current halo code
local haloex = {}
local render = render
local cam = cam
local lazyload
local matColor
local mat_Copy
local mat_Add
local mat_Sub
local rt_Stencil
local rt_Store
-- loading these only when needed, should not be too costly
lazyload = function()
lazyload = nil
matColor = Material( "model_color" )
mat_Copy = Material( "pp/copy" )
mat_Add = Material( "pp/add" )
mat_Sub = Material( "pp/sub" )
rt_Stencil = GetRenderTarget("halo_ex_stencil" .. os.clock(), ScrW()/8, ScrH()/8, true)
rt_Store = GetRenderTarget("halo_ex_store" .. os.clock(), ScrW(), ScrH(), true)
end
local List = {}
function haloex.Add( ents, color, blurx, blury, passes, add, ignorez, amount, spherical, shape )
if add == nil then add = true end
if ignorez == nil then ignorez = false end
local t =
{
Ents = ents,
Color = color,
Hidden = when_hidden,
BlurX = blurx or 2,
BlurY = blury or 2,
DrawPasses = passes or 1,
Additive = add,
IgnoreZ = ignorez,
Amount = amount or 1,
SphericalSize = spherical or 1,
Shape = shape or 1,
}
table.insert( List, t )
end
function haloex.Render( entry )
local OldRT = render.GetRenderTarget()
-- Copy what's currently on the screen to another texture
render.CopyRenderTargetToTexture( rt_Store )
-- Clear the colour and the stencils, not the depth
if ( entry.Additive ) then
render.Clear( 0, 0, 0, 255, false, true )
else
render.Clear( 255, 255, 255, 255, false, true )
end
-- FILL STENCIL
-- Write to the stencil..
cam.Start3D( EyePos(), EyeAngles() )
cam.IgnoreZ( entry.IgnoreZ )
render.OverrideDepthEnable( true, false ) -- Don't write depth
render.SetStencilEnable( true );
render.SetStencilFailOperation( STENCILOPERATION_KEEP );
render.SetStencilZFailOperation( STENCILOPERATION_KEEP );
render.SetStencilPassOperation( STENCILOPERATION_REPLACE );
render.SetStencilCompareFunction( STENCILCOMPARISONFUNCTION_ALWAYS );
render.SetStencilWriteMask( 1 );
render.SetStencilReferenceValue( 1 );
render.SetBlend( 0 ); -- don't render any colour
for k, v in pairs( entry.Ents ) do
if ( not IsValid( v ) ) then goto CONTINUE end
if v.pacDrawModel then
v:pacDrawModel()
else
v:DrawModel()
end
::CONTINUE::
end
cam.End3D()
-- FILL COLOUR
-- Write to the colour buffer
cam.Start3D( EyePos(), EyeAngles() )
render.MaterialOverride( matColor )
cam.IgnoreZ( entry.IgnoreZ )
render.SetStencilEnable( true );
render.SetStencilWriteMask( 0 );
render.SetStencilReferenceValue( 0 );
render.SetStencilTestMask( 1 );
render.SetStencilFailOperation( STENCILOPERATION_KEEP );
render.SetStencilPassOperation( STENCILOPERATION_KEEP );
render.SetStencilZFailOperation( STENCILOPERATION_KEEP );
render.SetStencilCompareFunction( STENCILCOMPARISONFUNCTION_NOTEQUAL );
for k, v in pairs( entry.Ents ) do
if ( not IsValid( v ) ) then goto CONTINUE end
render.SetColorModulation( entry.Color.r/255, entry.Color.g/255, entry.Color.b/255 )
render.SetBlend( entry.Color.a/255 );
if v.pacDrawModel then
v:pacDrawModel()
else
v:DrawModel()
end
::CONTINUE::
end
render.MaterialOverride( nil )
render.SetStencilEnable( false );
cam.End3D()
-- BLUR IT
render.CopyRenderTargetToTexture( rt_Stencil )
render.OverrideDepthEnable( false, false )
render.SetStencilEnable( false );
render.BlurRenderTarget( rt_Stencil, entry.BlurX, entry.BlurY, math.Clamp(entry.Amount,0,32) )
-- Put our scene back
render.SetRenderTarget( OldRT )
render.SetColorModulation( 1, 1, 1 )
render.SetStencilEnable( false );
render.OverrideDepthEnable( true, false )
render.SetBlend( 1 );
mat_Copy:SetTexture( "$basetexture", rt_Store )
render.SetMaterial( mat_Copy )
render.DrawScreenQuad()
-- DRAW IT TO THE SCEEN
render.SetStencilEnable( true );
render.SetStencilWriteMask( 0 );
render.SetStencilReferenceValue( 0 );
render.SetStencilTestMask( 1 );
render.SetStencilFailOperation( STENCILOPERATION_KEEP );
render.SetStencilPassOperation( STENCILOPERATION_KEEP );
render.SetStencilZFailOperation( STENCILOPERATION_KEEP );
render.SetStencilCompareFunction( STENCILCOMPARISONFUNCTION_EQUAL );
if ( entry.Additive ) then
mat_Add:SetTexture( "$basetexture", rt_Stencil )
render.SetMaterial( mat_Add )
else
mat_Sub:SetTexture( "$basetexture", rt_Stencil )
render.SetMaterial( mat_Sub )
end
for i=0, math.Clamp(entry.DrawPasses,0,32) do
local s = entry.SphericalSize
local n = (i / entry.DrawPasses)
local x = math.sin(n * math.pi * 2) * s
local y = math.cos(n * math.pi * 2) * s
render.DrawScreenQuadEx(
math.Clamp(x, s * -entry.Shape, s * entry.Shape),
math.Clamp(y, s * -entry.Shape, s * entry.Shape),
ScrW(),
ScrH()
)
end
-- PUT EVERYTHING BACK HOW WE FOUND IT
render.SetStencilWriteMask( 0 );
render.SetStencilReferenceValue( 0 );
render.SetStencilTestMask( 0 );
render.SetStencilEnable( false )
render.OverrideDepthEnable( false )
render.SetBlend( 1 )
cam.IgnoreZ( false )
end
pac.AddHook( "PostDrawEffects", "RenderHaloexs", function()
if not List[1] then return end
if lazyload then lazyload() end
for k, v in pairs( List ) do
haloex.Render( v )
end
List = {}
end )
if pac.haloex then
pac.haloex = haloex
end
return haloex

View File

@@ -0,0 +1,359 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
luadata by CapsAdmin (fuck copyright, do what you want with this)
-- encodes table to string
string luadata.Encode(tbl)
-- decodes string to table
-- it will throw an error if there's a syntax error in the table
table luadata.Decode(str)
-- writes the table to file ( it's just "file.Write(path, luadata.Encode(str))" )
nil luadata.WriteFile(path, tbl)
table luadata.ReadFile(path)
-- returns a string of how the variable is typically initialized
string luadata.ToString(var)
-- will let you add your own tostring function for a custom type
-- if you have made a custom data object, you can do this "mymatrix.LuaDataType = "Matrix33""
-- and it will make luadata.Type return that instead
nil luadata.SetModifier(type, callback)
]]
--- luajit bytecode firewall --
local opcode_checker
do
local bcnames
local jit = jit or require("jit")
local ver = jit and jit.version_num or 0
-- what are these magic versions?
if ver >= 20000 and ver <= 20009 then
bcnames = "ISLT ISGE ISLE ISGT ISEQV ISNEV ISEQS ISNES ISEQN ISNEN ISEQP ISNEP ISTC ISFC IST ISF MOV NOT UNM LEN ADDVN SUBVN MULVN DIVVN MODVN ADDNV SUBNV MULNV DIVNV MODNV ADDVV SUBVV MULVV DIVVV MODVV POW CAT KSTR KCDATAKSHORTKNUM KPRI KNIL UGET USETV USETS USETN USETP UCLO FNEW TNEW TDUP GGET GSET TGETV TGETS TGETB TSETV TSETS TSETB TSETM CALLM CALL CALLMTCALLT ITERC ITERN VARG ISNEXTRETM RET RET0 RET1 FORI JFORI FORL IFORL JFORL ITERL IITERLJITERLLOOP ILOOP JLOOP JMP FUNCF IFUNCFJFUNCFFUNCV IFUNCVJFUNCVFUNCC FUNCCW"
elseif ver==20100 then -- LuaJIT 2.1.0-beta3
bcnames = "ISLT ISGE ISLE ISGT ISEQV ISNEV ISEQS ISNES ISEQN ISNEN ISEQP ISNEP ISTC ISFC IST ISF ISTYPEISNUM MOV NOT UNM LEN ADDVN SUBVN MULVN DIVVN MODVN ADDNV SUBNV MULNV DIVNV MODNV ADDVV SUBVV MULVV DIVVV MODVV POW CAT KSTR KCDATAKSHORTKNUM KPRI KNIL UGET USETV USETS USETN USETP UCLO FNEW TNEW TDUP GGET GSET TGETV TGETS TGETB TGETR TSETV TSETS TSETB TSETM TSETR CALLM CALL CALLMTCALLT ITERC ITERN VARG ISNEXTRETM RET RET0 RET1 FORI JFORI FORL IFORL JFORL ITERL IITERLJITERLLOOP ILOOP JLOOP JMP FUNCF IFUNCFJFUNCFFUNCV IFUNCVJFUNCVFUNCC FUNCCW"
end
local jutil = jit.util
if not jutil then
local ok, _jutil = pcall(require,'jit.util')
if not ok then
bcnames = nil
else
jutil = _jutil
end
end
if not bcnames then
if not jutil then
print("[luadata] Verifier could not be loaded. luadata may cause infinite loops.")
else
ErrorNoHalt"LUADATA SECURITY WARNING: Unable to load verifier, update me!\n"
end
opcode_checker = function() return function() return true end end
else
local band = bit.band
local opcodes = {}
--extract opcode names
for str in bcnames:gmatch "......" do
str = str:gsub("%s", "")
table.insert(opcodes, str)
end
local function getopnum(opname)
for k, v in next, opcodes do
if v == opname then
return k
end
end
error("not found: " .. opname)
end
local function getop(func, pc)
local ins = jutil.funcbc(func, pc)
return ins and (band(ins, 0xff)+1)
end
opcode_checker = function(white)
local opwhite = {}
for i=0,#opcodes do table.insert(opwhite, false) end
local function iswhitelisted(opnum)
local ret = opwhite[opnum]
if ret == nil then
error("opcode not found " .. opnum)
end
return ret
end
local function add_whitelist(num)
if opwhite[num] == nil then
error "invalid opcode num"
end
opwhite[num] = true
end
for line in white:gmatch '[^\r\n]+' do
local opstr_towhite = line:match '[%w]+'
if opstr_towhite and opstr_towhite:len() > 0 then
local whiteopnum = getopnum(opstr_towhite)
add_whitelist(whiteopnum)
assert(iswhitelisted(whiteopnum))
end
end
local function checker_function(func,max_opcodes)
max_opcodes = max_opcodes or math.huge
for i = 1, max_opcodes do
local ret = getop(func, i)
if not ret then
return true
end
if not iswhitelisted(ret) then
--error("non-whitelisted: " .. )
return false,"non-whitelisted: "..opcodes[ret]
end
end
return false,"checked max_opcodes"
end
return checker_function
end
end
end
local whitelist = [[TNEW
TDUP
TSETV
TSETS
TSETB
TSETM
KSTR
KCDATA
KSHORT
KNUM
KPRI
KNIL
UNM
GGET
CALL
RET1]]
local is_func_ok = opcode_checker(whitelist)
-------------------------------
local luadata = {}
local s = luadata
luadata.is_func_ok = is_func_ok
luadata.EscapeSequences = {
[("\a"):byte()] = [[\a]],
[("\b"):byte()] = [[\b]],
[("\f"):byte()] = [[\f]],
[("\t"):byte()] = [[\t]],
[("\r"):byte()] = [[\r]],
[("\v"):byte()] = [[\v]],
}
local tab = 0
luadata.Types = {
["number"] = function(var)
return ("%s"):format(var)
end,
["string"] = function(var)
return ("%q"):format(var)
end,
["boolean"] = function(var)
return ("%s"):format(var and "true" or "false")
end,
["Vector"] = function(var)
return ("Vector(%s, %s, %s)"):format(var.x, var.y, var.z)
end,
["Angle"] = function(var)
return ("Angle(%s, %s, %s)"):format(var.p, var.y, var.r)
end,
["table"] = function(var)
if
isnumber(var.r) and
isnumber(var.g) and
isnumber(var.b) and
isnumber(var.a)
then
return ("Color(%s, %s, %s, %s)"):format(var.r, var.g, var.b, var.a)
end
tab = tab + 1
local str = luadata.Encode(var, true)
tab = tab - 1
return str
end,
}
function luadata.SetModifier(type, callback)
luadata.Types[type] = callback
end
function luadata.Type(var)
local t
if IsEntity(var) then
if var:IsValid() then
t = "Entity"
else
t = "NULL"
end
else
t = type(var)
end
if t == "table" then
if var.LuaDataType then
t = var.LuaDataType
end
end
return t
end
function luadata.ToString(var)
local func = s.Types[s.Type(var)]
return func and func(var)
end
function luadata.Encode(tbl, __brackets)
if luadata.Hushed then return end
local str = __brackets and "{\n" or ""
for key, value in pairs(tbl) do
value = s.ToString(value)
key = s.ToString(key)
if key and value and key ~= "__index" then
str = str .. ("\t"):rep(tab) .. ("[%s] = %s,\n"):format(key, value)
end
end
str = str .. ("\t"):rep(tab-1) .. (__brackets and "}" or "")
return str
end
local env = {
Vector=Vector,
Angle=Angle,
Color=Color,
--Entity=Entity,
}
-- TODO: Bytecode analysis for bad loop and string functions?
function luadata.Decode(str,nojail)
local func = CompileString(string.format("return { %s }",str), "luadata_decode", false)
if isstring(func) then
--ErrorNoHalt("Luadata decode syntax: "..tostring(func):gsub("^luadata_decode","")..'\n')
return nil,func
end
if not nojail then
setfenv(func,env)
elseif istable(nojail) then
setfenv(func,nojail)
elseif isfunction(nojail) then
nojail( func )
end
local ok,err = is_func_ok( func )
if not ok or err then
err = err or "invalid opcodes detected"
--ErrorNoHalt("Luadata opcode: "..tostring(err):gsub("^luadata_decode","")..'\n')
return nil,err
end
local ok, err = xpcall(func,debug.traceback)
if not ok then
--ErrorNoHalt("Luadata decode: "..tostring(err):gsub("^luadata_decode","")..'\n')
return nil,err
end
if isfunction(nojail) then
nojail( func, err )
end
return err
end
do -- file extension
function luadata.WriteFile(path, tbl)
if tbl==nil or false --[[empty table!?]] then
if file.Exists(path,'DATA') then
file.Delete(path,'DATA')
return true
end
return false,"file does not exist"
end
local encoded = luadata.Encode(tbl)
file.Write(path, encoded)
--if not file.Exists(path,'DATA') then return false,"could not write" end
return encoded
end
function luadata.ReadFile(path,location,noinvalid)
local file = file.Read(path,location or 'DATA')
if not file then
if noinvalid then return end
return false,"invalid file"
end
return luadata.Decode(file)
end
end
return luadata

View File

@@ -0,0 +1,439 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local resource = {}
local luadata = include("luadata.lua")
local function llog(...)
print("[resource] ", ...)
end
local function wlog(...)
print("[resource warning] ", ...)
end
local function R(path)
if file.Exists(path, "DATA") then
return path
end
end
local function utility_CreateCallbackThing(cache)
cache = cache or {}
local self = {}
function self:check(path, callback, extra)
if cache[path] then
if cache[path].extra_callbacks then
for key, old in pairs(cache[path].extra_callbacks) do
local callback = extra[key]
if callback then
cache[path].extra_callbacks[key] = function(...)
old(...)
callback(...)
end
end
end
end
if cache[path].callback then
if not istable(cache[path].callback) then cache[path].callback = {cache[path].callback} end
table.insert(cache[path].callback, callback)
return true
end
end
end
function self:start(path, callback, extra)
if not istable(callback) then callback = {callback} end
cache[path] = {callback = table.Copy(callback), extra_callbacks = table.Copy(extra or {})}
end
function self:callextra(path, key, out)
if not cache[path] or not cache[path].extra_callbacks[key] then return end
return cache[path].extra_callbacks[key](out)
end
function self:stop(path, out, ...)
if not cache[path] then return end
if istable(cache[path].callback) then
for i, func in ipairs(cache[path].callback) do
func(out, ...)
end
elseif cache[path].callback then
cache[path].callback(out, ...)
end
cache[path] = out
end
function self:get(path)
return cache[path]
end
function self:uncache(path)
cache[path] = nil
end
return self
end
local DOWNLOAD_FOLDER = "pac3_cache/downloads/"
local etags_file = "pac3_cache/resource_etags.txt"
file.CreateDir(DOWNLOAD_FOLDER)
local maxAgeConvar = CreateConVar("pac_downloads_cache_maxage", "604800", FCVAR_ARCHIVE, "Maximum age of cache entries in seconds, default is 1 week.")
local function clearCacheAfter( time )
for _, fileName in ipairs(file.Find(DOWNLOAD_FOLDER .. "*", "DATA")) do
local fullPath = DOWNLOAD_FOLDER .. fileName
if file.Time(fullPath, "DATA") < time then
file.Delete(fullPath)
end
end
end
clearCacheAfter(os.time() - maxAgeConvar:GetInt())
local function rename_file(a, b)
local str_a = file.Read(a, "DATA")
file.Delete(a, "DATA")
file.Write(b, str_a)
return true
end
local function download(from, to, callback, on_fail, on_header, check_etag, etag_path_override, need_extension)
if check_etag then
local data = luadata.ReadFile(etags_file)
local etag = data[etag_path_override or from]
--llog("checking if ", etag_path_override or from, " has been modified. etag is: ", etag)
HTTP({
method = "HEAD",
url = pac.FixGMODUrl(from),
success = function(code, body, header)
local res = header.ETag or header["Last-Modified"]
if not res then return end
if res ~= etag then
if etag then
llog(from, ": etag has changed ", res)
else
llog(from, ": no previous etag stored", res)
end
download(from, to, callback, on_fail, on_header, nil, etag_path_override, need_extension)
else
--llog(from, ": etag is the same")
check_etag()
end
end,
})
return
end
local file
local allowed = {
[".txt"] = true,
[".jpg"] = true,
[".png"] = true,
[".vtf"] = true,
[".dat"] = true,
}
return pac.HTTPGet(
from,
function(body, len, header)
do
if need_extension then
local ext = header["Content-Type"] and (header["Content-Type"]:match(".-/(.-);") or header["Content-Type"]:match(".-/(.+)")) or "dat"
if ext == "jpeg" then ext = "jpg" end
if body:StartWith("VTF") then
ext = "vtf"
end
if allowed["." .. ext] then
to = to .. "." .. ext
else
to = to .. ".dat"
end
end
local file_, err = _G.file.Open(DOWNLOAD_FOLDER .. to .. "_temp.dat", "wb", "DATA")
file = file_
if not file then
llog("resource download error: ", err)
on_fail()
return false
end
local etag = header.ETag or header["Last-Modified"]
if etag then
local data = luadata.ReadFile(etags_file) or {}
data[etag_path_override or from] = etag
luadata.WriteFile(etags_file)
end
on_header(header)
end
file:Write(body)
file:Close()
local full_path = DOWNLOAD_FOLDER .. to .. "_temp.dat"
if full_path then
local ok, err = rename_file(full_path, full_path:gsub("(.+)_temp%.dat", "%1"))
if not ok then
llog("unable to rename %q: %s", full_path, err)
on_fail()
return
end
local full_path = R(DOWNLOAD_FOLDER .. to)
if full_path then
resource.BuildCacheFolderList(full_path:match(".+/(.+)"))
callback(full_path)
--llog("finished donwnloading ", from)
else
wlog("resource download error: %q not found!", DOWNLOAD_FOLDER .. to)
on_fail()
end
else
wlog("resource download error: %q not found!", DOWNLOAD_FOLDER .. to)
on_fail()
end
end,
function(...)
on_fail(...)
end
)
end
local cb = utility_CreateCallbackThing()
local ohno = false
function resource.Download(path, callback, on_fail, crc, check_etag)
on_fail = on_fail or function(reason) llog(path, ": ", reason) end
local url
local existing_path
local need_extension
if path:find("^.-://") then
local redownload = false
if path:StartWith("_") then
path = path:sub(2)
redownload = true
end
if not resource.url_cache_lookup then
resource.BuildCacheFolderList()
end
url = path
local crc = (crc or pac.Hash(path))
if not redownload and resource.url_cache_lookup[crc] then
path = resource.url_cache_lookup[crc]
existing_path = R(DOWNLOAD_FOLDER .. path)
need_extension = false
else
path = crc
existing_path = false
need_extension = true
end
end
if not existing_path then
check_etag = nil
end
if not ohno then
local old = callback
callback = function(path)
if old then old(path) end
end
end
if existing_path and not check_etag then
ohno = true
if isfunction(callback) then
callback(existing_path)
elseif istable(callback) then
for i, func in ipairs(callback) do
func(existing_path)
end
end
ohno = false
return true
end
if check_etag then
check_etag = function()
if ohno then return end
ohno = true
cb:callextra(path, "check_etag", existing_path)
ohno = false
cb:stop(path, existing_path)
cb:uncache(path)
end
end
if cb:check(path, callback, {on_fail = on_fail, check_etag = check_etag}) then return true end
cb:start(path, callback, {on_fail = on_fail, check_etag = check_etag})
if url then
if not check_etag then
-- llog("downloading ", url)
end
download(
url,
path,
function(...)
cb:stop(path, ...)
cb:uncache(path)
end,
function(...)
cb:callextra(path, "on_fail", ... or path .. " not found")
cb:uncache(path)
end,
function(header)
-- check file crc stuff here/
return true
end,
check_etag,
nil,
need_extension
)
return true
end
end
function resource.BuildCacheFolderList(file_name)
if not resource.url_cache_lookup then
local tbl = {}
for _, file_name in ipairs((file.Find(DOWNLOAD_FOLDER .. "*", "DATA"))) do
local name = file_name:match("(%w+)%.")
if name then
tbl[name] = file_name
else
llog("bad file in downloads/cache folder: ", file_name)
file.Delete(DOWNLOAD_FOLDER .. file_name)
end
end
resource.url_cache_lookup = tbl
end
if file_name then
resource.url_cache_lookup[file_name:match("(.-)%.")] = file_name
end
end
function resource.ClearDownloads()
local dirs = {}
for _, path in ipairs((vfs.Find(DOWNLOAD_FOLDER))) do
file.Delete(DOWNLOAD_FOLDER .. path)
end
resource.BuildCacheFolderList()
end
function resource.CheckDownloadedFiles()
local files = luadata.ReadFile(etags_file)
local count = table.Count(files)
llog("checking " .. count .. " files for updates..")
local i = 0
for path, etag in pairs(files) do
resource.Download(path, function() i = i + 1 if i == count then llog("done checking for file updates") end end, llog, nil, true)
end
end
if CLIENT then
local memory = {}
timer.Create("pac3_resource_gc", 0.25, 0, function()
for k,v in pairs(memory) do
if not v.ply:IsValid() then
memory[k] = nil
file.Delete(v.path)
end
end
end)
pac.AddHook("ShutDown", "resource_gc", function()
for _, name in ipairs((file.Find(DOWNLOAD_FOLDER .. "*", "DATA"))) do
file.Delete(DOWNLOAD_FOLDER .. name)
end
end)
function resource.DownloadTexture(url, callback, ply)
if not url:find("^.-://") then return end
return resource.Download(
url,
function(path)
local frames
local mat
if path:EndsWith(".vtf") then
local f = file.Open(path, "rb", "DATA")
f:Seek(24)
frames = f:ReadShort()
f:Close()
mat = CreateMaterial(tostring({}), "VertexLitGeneric", {})
mat:SetTexture("$basetexture", "../data/" .. path)
else
mat = Material("../data/" .. path, "mips smooth noclamp")
end
local tex = mat:GetTexture("$basetexture")
if tex then
callback(tex, frames)
memory[url] = {ply = ply, path = path}
elseif ply == pac.LocalPlayer then
pac.Message(Color(255, 50, 50), "$basetexture from ", url, " is nil")
end
end,
function()
end
)
end
end
return resource

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,670 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local math_huge = math.huge
local math_frexp = math.frexp
local math_ldexp = math.ldexp
local math_floor = math.floor
local math_min = math.min
local math_max = math.max
local bit_rshift = bit.rshift
--- StringStream type
-- @name StringStream
-- @class type
-- @libtbl ss_methods
local ss_methods = {}
local ss_meta = {
__index = ss_methods,
__metatable = "StringStream",
__tostring = function(self)
return string.format("Stringstream [%u,%u]", self:tell(), self:size())
end
}
local ss_methods_big = setmetatable({},{__index=ss_methods})
local ss_meta_big = {
__index = ss_methods_big,
__metatable = "StringStream",
__tostring = function(self)
return string.format("Stringstream [%u,%u]", self:tell(), self:size())
end
}
local function StringStream(stream, i, endian)
local ret = setmetatable({
index = 1,
subindex = 1
}, ss_meta)
if stream~=nil then
assert(isstring(stream), "stream must be a string")
ret:write(stream)
if i~=nil then
assert(isnumber(i), "i must be a number")
ret:seek(i)
else
ret:seek(1)
end
end
if endian~=nil then
assert(isstring(endian), "endian must be a string")
ret:setEndian(endian)
end
return ret
end
--Credit https://stackoverflow.com/users/903234/rpfeltz
--Bugfixes and IEEE754Double credit to me
local function PackIEEE754Float(number)
if number == 0 then
return 0x00, 0x00, 0x00, 0x00
elseif number == math_huge then
return 0x00, 0x00, 0x80, 0x7F
elseif number == -math_huge then
return 0x00, 0x00, 0x80, 0xFF
elseif number ~= number then
return 0x00, 0x00, 0xC0, 0xFF
else
local sign = 0x00
if number < 0 then
sign = 0x80
number = -number
end
local mantissa, exponent = math_frexp(number)
exponent = exponent + 0x7F
if exponent <= 0 then
mantissa = math_ldexp(mantissa, exponent - 1)
exponent = 0
elseif exponent > 0 then
if exponent >= 0xFF then
return 0x00, 0x00, 0x80, sign + 0x7F
elseif exponent == 1 then
exponent = 0
else
mantissa = mantissa * 2 - 1
exponent = exponent - 1
end
end
mantissa = math_floor(math_ldexp(mantissa, 23) + 0.5)
return mantissa % 0x100,
bit_rshift(mantissa, 8) % 0x100,
(exponent % 2) * 0x80 + bit_rshift(mantissa, 16),
sign + bit_rshift(exponent, 1)
end
end
local function UnpackIEEE754Float(b4, b3, b2, b1)
local exponent = (b1 % 0x80) * 0x02 + bit_rshift(b2, 7)
local mantissa = math_ldexp(((b2 % 0x80) * 0x100 + b3) * 0x100 + b4, -23)
if exponent == 0xFF then
if mantissa > 0 then
return 0 / 0
else
if b1 >= 0x80 then
return -math_huge
else
return math_huge
end
end
elseif exponent > 0 then
mantissa = mantissa + 1
else
exponent = exponent + 1
end
if b1 >= 0x80 then
mantissa = -mantissa
end
return math_ldexp(mantissa, exponent - 0x7F)
end
local function PackIEEE754Double(number)
if number == 0 then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
elseif number == math_huge then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F
elseif number == -math_huge then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF
elseif number ~= number then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF
else
local sign = 0x00
if number < 0 then
sign = 0x80
number = -number
end
local mantissa, exponent = math_frexp(number)
exponent = exponent + 0x3FF
if exponent <= 0 then
mantissa = math_ldexp(mantissa, exponent - 1)
exponent = 0
elseif exponent > 0 then
if exponent >= 0x7FF then
return 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, sign + 0x7F
elseif exponent == 1 then
exponent = 0
else
mantissa = mantissa * 2 - 1
exponent = exponent - 1
end
end
mantissa = math_floor(math_ldexp(mantissa, 52) + 0.5)
return mantissa % 0x100,
math_floor(mantissa / 0x100) % 0x100, --can only rshift up to 32 bit numbers. mantissa is too big
math_floor(mantissa / 0x10000) % 0x100,
math_floor(mantissa / 0x1000000) % 0x100,
math_floor(mantissa / 0x100000000) % 0x100,
math_floor(mantissa / 0x10000000000) % 0x100,
(exponent % 0x10) * 0x10 + math_floor(mantissa / 0x1000000000000),
sign + bit_rshift(exponent, 4)
end
end
local function UnpackIEEE754Double(b8, b7, b6, b5, b4, b3, b2, b1)
local exponent = (b1 % 0x80) * 0x10 + bit_rshift(b2, 4)
local mantissa = math_ldexp(((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8, -52)
if exponent == 0x7FF then
if mantissa > 0 then
return 0 / 0
else
if b1 >= 0x80 then
return -math_huge
else
return math_huge
end
end
elseif exponent > 0 then
mantissa = mantissa + 1
else
exponent = exponent + 1
end
if b1 >= 0x80 then
mantissa = -mantissa
end
return math_ldexp(mantissa, exponent - 0x3FF)
end
--- Sets the endianness of the string stream
--@param endian The endianness of number types. "big" or "little" (default "little")
function ss_methods:setEndian(endian)
if endian == "little" then
debug.setmetatable(self, ss_meta)
elseif endian == "big" then
debug.setmetatable(self, ss_meta_big)
else
error("Invalid endian specified", 2)
end
end
--- Writes the given string and advances the buffer pointer.
--@param data A string of data to write
function ss_methods:write(data)
if self.index > #self then -- Most often case
self[self.index] = data
self.index = self.index + 1
self.subindex = 1
else
local i = 1
local length = #data
while length > 0 do
if self.index > #self then -- End of buffer
self[self.index] = string.sub(data, i)
self.index = self.index + 1
self.subindex = 1
break
else
local cur = self[self.index]
local sublength = math_min(#cur - self.subindex + 1, length)
self[self.index] = string.sub(cur,1,self.subindex-1) .. string.sub(data,i,i+sublength-1) .. string.sub(cur,self.subindex+sublength)
length = length - sublength
i = i + sublength
if length > 0 then
self.index = self.index + 1
self.subindex = 1
else
self.subindex = self.subindex + sublength
end
end
end
end
end
--- Reads the specified number of bytes from the buffer and advances the buffer pointer.
--@param length How many bytes to read
--@return A string containing the bytes
function ss_methods:read(length)
local ret = {}
while length > 0 do
local cur = self[self.index]
if cur then
if self.subindex == 1 and length >= #cur then
ret[#ret+1] = cur
self.index = self.index + 1
length = length - #cur
else
local sublength = math_min(#cur - self.subindex + 1, length)
ret[#ret+1] = string.sub(cur, self.subindex, self.subindex + sublength - 1)
length = length - sublength
if length > 0 then
self.index = self.index + 1
self.subindex = 1
else
self.subindex = self.subindex + sublength
end
end
else
break
end
end
return table.concat(ret)
end
--- Sets internal pointer to i. The position will be clamped to [1, buffersize+1]
--@param i The position
function ss_methods:seek(pos)
if pos < 1 then error("Index must be 1 or greater", 2) end
self.index = #self+1
self.subindex = 1
local length = 0
for i, v in ipairs(self) do
length = length + #v
if length >= pos then
self.index = i
self.subindex = pos - (length - #v)
break
end
end
end
--- Move the internal pointer by amount i
--@param length The offset
function ss_methods:skip(length)
while length>0 do
local cur = self[self.index]
if cur then
local sublength = math_min(#cur - self.subindex + 1, length)
length = length - sublength
self.subindex = self.subindex + sublength
if self.subindex>#cur then
self.index = self.index + 1
self.subindex = 1
end
else
self.index = #self.index + 1
self.subindex = 1
break
end
end
while length<0 do
local cur = self[self.index]
if cur then
local sublength = math_max(-self.subindex, length)
length = length - sublength
self.subindex = self.subindex + sublength
if self.subindex<1 then
self.index = self.index - 1
self.subindex = self[self.index] and #self[self.index] or 1
end
else
self.index = 1
self.subindex = 1
break
end
end
end
--- Returns the internal position of the byte reader.
--@return The buffer position
function ss_methods:tell()
local length = 0
for i=1, self.index-1 do
length = length + #self[i]
end
return length + self.subindex
end
--- Tells the size of the byte stream.
--@return The buffer size
function ss_methods:size()
local length = 0
for i, v in ipairs(self) do
length = length + #v
end
return length
end
--- Reads an unsigned 8-bit (one byte) integer from the byte stream and advances the buffer pointer.
--@return The uint8 at this position
function ss_methods:readUInt8()
return string.byte(self:read(1))
end
function ss_methods_big:readUInt8()
return string.byte(self:read(1))
end
--- Reads an unsigned 16 bit (two byte) integer from the byte stream and advances the buffer pointer.
--@return The uint16 at this position
function ss_methods:readUInt16()
local a,b = string.byte(self:read(2), 1, 2)
return b * 0x100 + a
end
function ss_methods_big:readUInt16()
local a,b = string.byte(self:read(2), 1, 2)
return a * 0x100 + b
end
--- Reads an unsigned 32 bit (four byte) integer from the byte stream and advances the buffer pointer.
--@return The uint32 at this position
function ss_methods:readUInt32()
local a,b,c,d = string.byte(self:read(4), 1, 4)
return d * 0x1000000 + c * 0x10000 + b * 0x100 + a
end
function ss_methods_big:readUInt32()
local a,b,c,d = string.byte(self:read(4), 1, 4)
return a * 0x1000000 + b * 0x10000 + c * 0x100 + d
end
--- Reads a signed 8-bit (one byte) integer from the byte stream and advances the buffer pointer.
--@return The int8 at this position
function ss_methods:readInt8()
local x = self:readUInt8()
if x>=0x80 then x = x - 0x100 end
return x
end
--- Reads a signed 16-bit (two byte) integer from the byte stream and advances the buffer pointer.
--@return The int16 at this position
function ss_methods:readInt16()
local x = self:readUInt16()
if x>=0x8000 then x = x - 0x10000 end
return x
end
--- Reads a signed 32-bit (four byte) integer from the byte stream and advances the buffer pointer.
--@return The int32 at this position
function ss_methods:readInt32()
local x = self:readUInt32()
if x>=0x80000000 then x = x - 0x100000000 end
return x
end
--- Reads a 4 byte IEEE754 float from the byte stream and advances the buffer pointer.
--@return The float32 at this position
function ss_methods:readFloat()
return UnpackIEEE754Float(string.byte(self:read(4), 1, 4))
end
function ss_methods_big:readFloat()
local a,b,c,d = string.byte(self:read(4), 1, 4)
return UnpackIEEE754Float(d, c, b, a)
end
--- Reads a 8 byte IEEE754 double from the byte stream and advances the buffer pointer.
--@return The double at this position
function ss_methods:readDouble()
return UnpackIEEE754Double(string.byte(self:read(8), 1, 8))
end
function ss_methods_big:readDouble()
local a,b,c,d,e,f,g,h = string.byte(self:read(8), 1, 8)
return UnpackIEEE754Double(h, g, f, e, d, c, b, a)
end
--- Reads until the given byte and advances the buffer pointer.
--@param byte The byte to read until (in number form)
--@return The string of bytes read
function ss_methods:readUntil(byte)
byte = string.char(byte)
local ret = {}
for i=self.index, #self do
local cur = self[self.index]
local find = string.find(cur, byte, self.subindex, true)
if find then
ret[#ret+1] = string.sub(cur, self.subindex, find)
self.subindex = find+1
if self.subindex > #cur then
self.index = self.index + 1
self.subindex = 1
end
break
else
if self.subindex == 1 then
ret[#ret+1] = cur
else
ret[#ret+1] = string.sub(cur, self.subindex)
end
self.index = self.index + 1
self.subindex = 1
end
end
return table.concat(ret)
end
--- returns a null terminated string, reads until "\x00" and advances the buffer pointer.
--@return The string of bytes read
function ss_methods:readString()
local s = self:readUntil(0)
return string.sub(s, 1, #s-1)
end
--- Writes a byte to the buffer and advances the buffer pointer.
--@param x An int8 to write
function ss_methods:writeInt8(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x100 end
self:write(string.char(x%0x100))
end
--- Writes a short to the buffer and advances the buffer pointer.
--@param x An int16 to write
function ss_methods:writeInt16(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x10000 end
self:write(string.char(x%0x100, bit_rshift(x, 8)%0x100))
end
function ss_methods_big:writeInt16(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x10000 end
self:write(bit_rshift(x, 8)%0x100, string.char(x%0x100))
end
--- Writes an int to the buffer and advances the buffer pointer.
--@param x An int32 to write
function ss_methods:writeInt32(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x100000000 end
self:write(string.char(x%0x100, bit_rshift(x, 8)%0x100, bit_rshift(x, 16)%0x100, bit_rshift(x, 24)%0x100))
end
function ss_methods_big:writeInt32(x)
if x==math_huge or x==-math_huge or x~=x then error("Can't convert error float to integer!", 2) end
if x < 0 then x = x + 0x100000000 end
self:write(string.char(bit_rshift(x, 24)%0x100, bit_rshift(x, 16)%0x100, bit_rshift(x, 8)%0x100), x%0x100)
end
--- Writes a 4 byte IEEE754 float to the byte stream and advances the buffer pointer.
--@param x The float to write
function ss_methods:writeFloat(x)
self:write(string.char(PackIEEE754Float(x)))
end
function ss_methods_big:writeFloat(x)
local a,b,c,d = PackIEEE754Float(x)
self:write(string.char(d,c,b,a))
end
--- Writes a 8 byte IEEE754 double to the byte stream and advances the buffer pointer.
--@param x The double to write
function ss_methods:writeDouble(x)
self:write(string.char(PackIEEE754Double(x)))
end
function ss_methods_big:writeDouble(x)
local a,b,c,d,e,f,g,h = PackIEEE754Double(x)
self:write(string.char(h,g,f,e,d,c,b,a))
end
--- Writes a string to the buffer putting a null at the end and advances the buffer pointer.
--@param string The string of bytes to write
function ss_methods:writeString(string)
self:write(string)
self:write("\0")
end
--- Returns the buffer as a string
--@return The buffer as a string
function ss_methods:getString()
return table.concat(self)
end
do
do
function ss_methods:writeBool(b)
self:writeInt8(b and 1 or 0)
end
function ss_methods:readBool()
return self:readInt8() == 1
end
end
do
function ss_methods:writeVector(val)
self:writeDouble(val.x)
self:writeDouble(val.y)
self:writeDouble(val.z)
end
function ss_methods:readVector()
local x = self:readDouble()
local y = self:readDouble()
local z = self:readDouble()
return Vector(x,y,z)
end
end
do
function ss_methods:writeAngle(val)
self:writeDouble(val.p)
self:writeDouble(val.y)
self:writeDouble(val.r)
end
function ss_methods:readAngle()
local x = self:readDouble()
local y = self:readDouble()
local z = self:readDouble()
return Angle(x,y,z)
end
end
do
function ss_methods:writeColor(val)
self:writeDouble(val.r)
self:writeDouble(val.g)
self:writeDouble(val.b)
self:writeDouble(val.a)
end
function ss_methods:readColor()
local r = self:readDouble()
local g = self:readDouble()
local b = self:readDouble()
local a = self:readDouble()
return Color(r,g,b,a)
end
end
do
function ss_methods:writeEntity(val)
self:writeInt32(val:EntIndex())
end
function ss_methods:readEntity()
return Entity(self:readInt32())
end
end
function ss_methods:writeTable(tab)
for k, v in pairs( tab ) do
self:writeType( k )
self:writeType( v )
end
self:writeType( nil )
end
function ss_methods:readTable()
local tab = {}
while true do
local k = self:readType()
if k == nil then
return tab
end
tab[k] = self:readType()
end
end
local write_functions = {
[TYPE_NIL] = function(s, t, v) s:writeInt8( t ) end,
[TYPE_STRING] = function(s, t, v) s:writeInt8( t ) s:writeString( v ) end,
[TYPE_NUMBER] = function(s, t, v) s:writeInt8( t ) s:writeDouble( v ) end,
[TYPE_TABLE] = function(s, t, v) s:writeInt8( t ) s:writeTable( v ) end,
[TYPE_BOOL] = function(s, t, v) s:writeInt8( t ) s:writeBool( v ) end,
[TYPE_VECTOR] = function(s, t, v) s:writeInt8( t ) s:writeVector( v ) end,
[TYPE_ANGLE] = function(s, t, v) s:writeInt8( t ) s:writeAngle( v ) end,
[TYPE_COLOR] = function(s, t, v) s:writeInt8( t ) s:writeColor( v ) end,
[TYPE_ENTITY] = function(s, t, v) s:writeInt8( t ) s:writeEntity( v ) end,
}
function ss_methods:writeType( v )
local typeid = nil
if IsColor(v) then
typeid = TYPE_COLOR
else
typeid = TypeID(v)
end
local func = write_functions[typeid]
if func then
return func(self, typeid, v)
end
error("StringStream:writeType: Couldn't write " .. type(v) .. " (type " .. typeid .. ")")
end
local read_functions = {
[TYPE_NIL] = function(s) return nil end,
[TYPE_STRING] = function(s) return s:readString() end,
[TYPE_NUMBER] = function(s) return s:readDouble() end,
[TYPE_TABLE] = function(s) return s:readTable() end,
[TYPE_BOOL] = function(s) return s:readBool() end,
[TYPE_VECTOR] = function(s) return s:readVector() end,
[TYPE_ANGLE] = function(s) return s:readAngle() end,
[TYPE_COLOR] = function(s) return s:readColor() end,
[TYPE_ENTITY] = function(s) return s:readEntity() end,
}
function ss_methods:readType( typeid )
typeid = typeid or self:readUInt8(8)
local func = read_functions[typeid]
if func then
return func(self)
end
error("StringStream:readType: Couldn't read type " .. tostring(typeid))
end
end
return StringStream

View File

@@ -0,0 +1,123 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local crypto = include("pac3/libraries/urlobj/crypto.lua")
local CACHE = {}
local function CreateCache(cacheId)
local cache = {}
setmetatable(cache, { __index = CACHE })
cache:Initialize(cacheId)
return cache
end
function CACHE:Initialize(cacheId)
self.Version = 3 -- Update this if the crypto library changes
self.Path = "pac3_cache/" .. string.lower(cacheId)
file.CreateDir(self.Path)
end
function CACHE:AddItem(itemId, data)
local hash = self:GetItemIdHash(itemId)
local path = self.Path .. "/" .. hash .. ".txt"
local key = self:GetItemIdEncryptionKey(itemId)
-- Version
local f = file.Open(path, "wb", "DATA")
if not f then return end
f:WriteLong(self.Version)
-- Header
local compressedItemId = util.Compress(itemId)
local entryItemId = crypto.EncryptString(compressedItemId, key)
f:WriteLong(#entryItemId)
f:Write(entryItemId, #entryItemId)
-- Data
local compressedData = util.Compress(data)
data = crypto.EncryptString(compressedData, key)
f:WriteLong(#data)
f:Write(data, #data)
f:Close()
end
function CACHE:Clear()
for _, fileName in ipairs(file.Find(self.Path .. "/*", "DATA")) do
file.Delete(self.Path .. "/" .. fileName)
end
end
function CACHE:ClearBefore(time)
for _, fileName in ipairs(file.Find(self.Path .. "/*", "DATA")) do
if file.Time(self.Path .. "/" .. fileName, "DATA") < time then
file.Delete(self.Path .. "/" .. fileName)
end
end
end
function CACHE:ContainsItem(itemId)
return self:GetItem(itemId) ~= nil
end
function CACHE:GetItem(itemId)
local hash = self:GetItemIdHash(itemId)
local path = self.Path .. "/" .. hash .. ".txt"
if not file.Exists(path, "DATA") then return nil end
local f = file.Open(path, "rb", "DATA")
if not f then return nil end
local key = self:GetItemIdEncryptionKey(itemId)
-- Version
local version = f:ReadLong()
if version ~= self.Version then
f:Close()
return nil
end
-- Header
local entryItemIdLength = f:ReadLong()
local entryItemId = crypto.DecryptString(f:Read(entryItemIdLength), key)
entryItemId = util.Decompress(entryItemId)
if itemId ~= entryItemId then
f:Close()
return nil
end
-- Data
local dataLength = f:ReadLong()
local data = f:Read(dataLength, key)
f:Close()
data = crypto.DecryptString(data, key)
data = util.Decompress(data)
return data
end
function CACHE:GetItemIdEncryptionKey(itemId)
return crypto.GenerateKey(string.reverse(itemId))
end
function CACHE:GetItemIdHash(itemId)
return string.format("%08x", tonumber(util.CRC(itemId)))
end
return CreateCache

View File

@@ -0,0 +1,505 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local crypto = {}
crypto.UInt8BlockSize = 64
crypto.UInt32BlockSize = 16
crypto.KeySize = crypto.UInt32BlockSize
local bit_band = bit.band
local bit_bxor = bit.bxor
local bit_rshift = bit.rshift
local math_ceil = math.ceil
local math_floor = math.floor
local math_random = math.random
local string_byte = string.byte
local string_char = string.char
local string_sub = string.sub
local table_concat = table.concat
-- byteCharacters [i] is faster than string_char (i)
local byteCharacters1 = {}
local byteCharacters2 = {}
local byteCharacters = byteCharacters1
for i = 0, 255 do byteCharacters1 [i] = string_char (i) end
for uint80 = 0, 255 do
for uint81 = 0, 255 do
byteCharacters2 [uint80 + uint81 * 256] = string_char (uint80, uint81)
end
end
function crypto.GenerateKey (seed, length)
length = length or crypto.KeySize
if isstring (seed) then
-- LOL ONLY 32 BITS OF ENTROPY
seed = tonumber (util.CRC (seed))
end
if seed then
math.randomseed (seed)
end
return crypto.GenerateRandomUInt32Array (length)
end
-- Encrypts a string
function crypto.EncryptString (inputString, keyArray)
local inputArray = crypto.StringToUInt32Array (inputString)
inputArray = crypto.PadUInt32Array (inputArray)
local outputArray = {}
outputArray = crypto.GenerateRandomUInt32Array (crypto.UInt32BlockSize, outputArray)
-- I have no idea either
local keyArray = crypto.CloneArray (keyArray)
keyArray = crypto.AppendArray (keyArray, keyArray)
keyArray = crypto.AppendArray (keyArray, keyArray)
keyArray = crypto.AppendArray (keyArray, keyArray)
keyArray = crypto.AppendArray (keyArray, keyArray)
keyArray = crypto.AppendArray (keyArray, keyArray)
keyArray = crypto.AppendArray (keyArray, keyArray)
crypto.XorInt32Arrays (outputArray, 1, keyArray, 1, crypto.UInt32BlockSize, outputArray, 1)
local inputArrayLength = #inputArray
local inputEndIndex = #inputArray
inputEndIndex = inputEndIndex - ((inputArrayLength / crypto.UInt32BlockSize) % 64) * crypto.UInt32BlockSize
local inputIndex = 1
while inputIndex <= inputEndIndex do
crypto.XorInt32Arrays3 (
inputArray, inputIndex,
outputArray, inputIndex,
keyArray, 1,
crypto.UInt32BlockSize * 64,
outputArray, crypto.UInt32BlockSize + inputIndex
)
inputIndex = inputIndex + crypto.UInt32BlockSize * 64
end
-- Remainder
inputEndIndex = #inputArray
while inputIndex <= inputEndIndex do
crypto.XorInt32Arrays3 (
inputArray, inputIndex,
outputArray, inputIndex,
keyArray, 1,
crypto.UInt32BlockSize,
outputArray, crypto.UInt32BlockSize + inputIndex
)
inputIndex = inputIndex + crypto.UInt32BlockSize
end
local outputString = crypto.Int32ArrayToString (outputArray)
return outputString
end
-- Decrypts a string
function crypto.DecryptString (inputString, keyArray)
local inputArray = crypto.StringToUInt32Array (inputString)
local inputIndex = #inputArray - crypto.UInt32BlockSize + 1
while inputIndex > crypto.UInt32BlockSize do
crypto.XorInt32Arrays3 (
inputArray, inputIndex,
inputArray, inputIndex - crypto.UInt32BlockSize,
keyArray, 1,
crypto.UInt32BlockSize,
inputArray, inputIndex
)
inputIndex = inputIndex - crypto.UInt32BlockSize
end
crypto.XorInt32Arrays (inputArray, 1, keyArray, 1, crypto.UInt32BlockSize, inputArray, 1)
inputArray = crypto.UnpadInt32Array (inputArray)
local outputArray = inputArray
local outputString = crypto.Int32ArrayToString (outputArray, crypto.UInt32BlockSize + 1)
return outputString
end
-- Pads an array in place
function crypto.PadUInt8Array (array)
local targetLength = math_ceil (#array / crypto.UInt8BlockSize) * crypto.UInt8BlockSize
if targetLength == #array then
targetLength = targetLength + crypto.UInt8BlockSize
end
array [#array + 1] = 0xFF
for i = #array + 1, targetLength do
array [i] = 0x00
end
array.n = #array
return array
end
-- Pads an array in place
function crypto.PadUInt32Array (array)
if array.n % 4 == 0 then
array [#array + 1] = 0x000000FF
elseif array.n % 4 == 1 then
array [#array] = array [#array] + 0x0000FF00
elseif array.n % 4 == 2 then
array [#array] = array [#array] + 0x00FF0000
elseif array.n % 4 == 3 then
array [#array] = array [#array] + 0xFF000000
end
local targetLength = math_ceil (#array / crypto.UInt32BlockSize) * crypto.UInt32BlockSize
for i = #array + 1, targetLength do
array [i] = 0x00000000
end
array.n = #array * 4
return array
end
-- Unpads an array in place
function crypto.UnpadUInt8Array (array)
for i = #array, 1, -1 do
if array [i] ~= 0x00 then break end
array [i] = nil
end
if array [#array] == 0xFF then
array [#array] = nil
end
array.n = #array
return array
end
-- Unpads an array in place
function crypto.UnpadUInt32Array (array)
return crypto.UnpadInt32Array (array)
end
-- Unpads an array in place
function crypto.UnpadInt32Array (array)
for i = #array, 1, -1 do
if array [i] ~= 0x00000000 then break end
array [i] = nil
end
array.n = #array * 4
if array [#array] < 0 then
array [#array] = array [#array] + 4294967296
end
if array [#array] - 0xFF000000 >= 0 then
array [#array] = array [#array] - 0xFF000000
array.n = array.n - 1
elseif array [#array] - 0x00FF0000 >= 0 then
array [#array] = array [#array] - 0x00FF0000
array.n = array.n - 2
elseif array [#array] - 0x0000FF00 >= 0 then
array [#array] = array [#array] - 0x0000FF00
array.n = array.n - 3
elseif array [#array] - 0x000000FF >= 0 then
array [#array] = nil
array.n = array.n - 4
end
return array
end
-- Array operations
-- Generates a random array of uint8s
function crypto.GenerateRandomUInt8Array (length, out)
out = out or {}
for i = 1, length do
out [#out + 1] = math_random (0, 0xFF)
end
return out
end
-- Generates a random array of uint32s
function crypto.GenerateRandomUInt32Array (length, out)
out = out or {}
for i = 1, length do
out [#out + 1] = math_random (0, 0xFFFFFFFF)
end
return out
end
-- Appends an array in place
function crypto.AppendArray (array, array1)
local array1Length = #array1
for i = 1, array1Length do
array [#array + 1] = array1 [i]
end
return array
end
-- Clones an array
function crypto.CloneArray (array, out)
out = out or {}
for i = 1, #array do
out [i] = array [i]
end
return out
end
-- Truncates an array in place
function crypto.TruncateArray (array, endIndex)
for i = endIndex + 1, #array do
array [i] = nil
end
return array
end
-- Xors an array with another
function crypto.XorArrays (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
return crypto.XorArrays2 (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
end
function crypto.XorArrays2 (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
out = out or {}
array1StartIndex = array1StartIndex or 1
array2StartIndex = array2StartIndex or 1
outStartIndex = outStartIndex or 1
local array2Index = array2StartIndex
local outputIndex = outStartIndex
local array1EndIndex = array1StartIndex + length - 1
for array1Index = array1StartIndex, array1EndIndex do
out [outputIndex] = bit_bxor (array1 [array1Index], array2 [array2Index])
array2Index = array2Index + 1
outputIndex = outputIndex + 1
end
return out
end
function crypto.XorArrays3 (array1, array1StartIndex, array2, array2StartIndex, array3, array3StartIndex, length, out, outStartIndex)
out = out or {}
array1StartIndex = array1StartIndex or 1
array2StartIndex = array2StartIndex or 1
array3StartIndex = array3StartIndex or 1
outStartIndex = outStartIndex or 1
local array2Index = array2StartIndex
local array3Index = array3StartIndex
local outputIndex = outStartIndex
local array1EndIndex = array1StartIndex + length - 1
for array1Index = array1StartIndex, array1EndIndex do
out [outputIndex] = bit_bxor (array1 [array1Index], array2 [array2Index], array3 [array3Index])
array2Index = array2Index + 1
array3Index = array3Index + 1
outputIndex = outputIndex + 1
end
return out
end
-- Xors an array with another
function crypto.XorUInt8Arrays (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
return crypto.XorArrays2 (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
end
function crypto.XorUInt8Arrays2 (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
return crypto.XorArrays2 (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
end
function crypto.XorUInt8Arrays3 (array1, array1StartIndex, array2, array2StartIndex, array3, array3StartIndex, length, out, outStartIndex)
return crypto.XorArrays3 (array1, array1StartIndex, array2, array2StartIndex, array3, array3StartIndex, length, out, outStartIndex)
end
-- Xors an array with another
function crypto.XorInt32Arrays (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
return crypto.XorArrays2 (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
end
function crypto.XorInt32Arrays2 (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
return crypto.XorArrays2 (array1, array1StartIndex, array2, array2StartIndex, length, out, outStartIndex)
end
function crypto.XorInt32Arrays3 (array1, array1StartIndex, array2, array2StartIndex, array3, array3StartIndex, length, out, outStartIndex)
return crypto.XorArrays3 (array1, array1StartIndex, array2, array2StartIndex, array3, array3StartIndex, length, out, outStartIndex)
end
-- Converts a string to an array of uint8s
function crypto.StringToUInt8Array (str, out)
out = out or {}
out.n = #str
-- ARE WE FAST YET?
for i = 1, #str, 64 do
out [i + 0], out [i + 1], out [i + 2], out [i + 3],
out [i + 4], out [i + 5], out [i + 6], out [i + 7],
out [i + 8], out [i + 9], out [i + 10], out [i + 11],
out [i + 12], out [i + 13], out [i + 14], out [i + 15],
out [i + 16], out [i + 17], out [i + 18], out [i + 19],
out [i + 20], out [i + 21], out [i + 22], out [i + 23],
out [i + 24], out [i + 25], out [i + 26], out [i + 27],
out [i + 28], out [i + 29], out [i + 30], out [i + 31],
out [i + 32], out [i + 33], out [i + 34], out [i + 35],
out [i + 36], out [i + 37], out [i + 38], out [i + 39],
out [i + 40], out [i + 41], out [i + 42], out [i + 43],
out [i + 44], out [i + 45], out [i + 46], out [i + 47],
out [i + 48], out [i + 49], out [i + 50], out [i + 51],
out [i + 52], out [i + 53], out [i + 54], out [i + 55],
out [i + 56], out [i + 57], out [i + 58], out [i + 59],
out [i + 60], out [i + 61], out [i + 62], out [i + 63] = string_byte (str, i, i + 63)
end
out = crypto.TruncateArray (out, #str)
return out
end
-- Converts an array of uint8s to a string destructively
function crypto.UInt8ArrayToString (array, startIndex)
startIndex = startIndex or 1
-- Process pairs of uint8s
local length = #array - startIndex + 1
local endIndex = #array
if length % 2 == 1 then endIndex = endIndex - 1 end
local j = startIndex
for i = startIndex, endIndex, 2 do
array [j] = byteCharacters2 [array [i] + array [i + 1] * 256]
j = j + 1
end
-- Process remaining uint8 if there is one
if length % 2 == 1 then
array [j] = byteCharacters [array [#array]]
j = j + 1
end
return table_concat (array, nil, startIndex, j - 1)
end
local oneOver64 = 1 / 64
-- Converts a string to an array of uint32s
function crypto.StringToUInt32Array (str, out)
out = out or {}
out.n = #str
local fullChunkCount = math_floor (#str * oneOver64)
local fullChunkCountMinusOne = fullChunkCount - 1
for i = 0, fullChunkCountMinusOne do
local uint80, uint81, uint82, uint83,
uint84, uint85, uint86, uint87,
uint88, uint89, uint810, uint811,
uint812, uint813, uint814, uint815,
uint816, uint817, uint818, uint819,
uint820, uint821, uint822, uint823,
uint824, uint825, uint826, uint827,
uint828, uint829, uint830, uint831,
uint832, uint833, uint834, uint835,
uint836, uint837, uint838, uint839,
uint840, uint841, uint842, uint843,
uint844, uint845, uint846, uint847,
uint848, uint849, uint850, uint851,
uint852, uint853, uint854, uint855,
uint856, uint857, uint858, uint859,
uint860, uint861, uint862, uint863 = string_byte (str, i * 64 + 1, i * 64 + 64)
out [i * 16 + 1] = uint80 + uint81 * 256 + uint82 * 65536 + uint83 * 16777216
out [i * 16 + 2] = uint84 + uint85 * 256 + uint86 * 65536 + uint87 * 16777216
out [i * 16 + 3] = uint88 + uint89 * 256 + uint810 * 65536 + uint811 * 16777216
out [i * 16 + 4] = uint812 + uint813 * 256 + uint814 * 65536 + uint815 * 16777216
out [i * 16 + 5] = uint816 + uint817 * 256 + uint818 * 65536 + uint819 * 16777216
out [i * 16 + 6] = uint820 + uint821 * 256 + uint822 * 65536 + uint823 * 16777216
out [i * 16 + 7] = uint824 + uint825 * 256 + uint826 * 65536 + uint827 * 16777216
out [i * 16 + 8] = uint828 + uint829 * 256 + uint830 * 65536 + uint831 * 16777216
out [i * 16 + 9] = uint832 + uint833 * 256 + uint834 * 65536 + uint835 * 16777216
out [i * 16 + 10] = uint836 + uint837 * 256 + uint838 * 65536 + uint839 * 16777216
out [i * 16 + 11] = uint840 + uint841 * 256 + uint842 * 65536 + uint843 * 16777216
out [i * 16 + 12] = uint844 + uint845 * 256 + uint846 * 65536 + uint847 * 16777216
out [i * 16 + 13] = uint848 + uint849 * 256 + uint850 * 65536 + uint851 * 16777216
out [i * 16 + 14] = uint852 + uint853 * 256 + uint854 * 65536 + uint855 * 16777216
out [i * 16 + 15] = uint856 + uint857 * 256 + uint858 * 65536 + uint859 * 16777216
out [i * 16 + 16] = uint860 + uint861 * 256 + uint862 * 65536 + uint863 * 16777216
end
if #str % 64 ~= 0 then
local startIndex = #str - #str % 64 + 1
for i = startIndex, #str, 4 do
local uint80, uint81, uint82, uint83 = string_byte (str, i, i + 3)
uint80, uint81, uint82, uint83 = uint80 or 0, uint81 or 0, uint82 or 0, uint83 or 0
out [#out + 1] = uint80 + uint81 * 256 + uint82 * 65536 + uint83 * 16777216
end
end
out = crypto.TruncateArray (out, math_ceil (#str * 0.25))
return out
end
-- Converts an array of int32s to a string
local bit = bit
local oneOver65536 = 1 / 65536
function crypto.Int32ArrayToString (array, startIndex)
startIndex = startIndex or 1
local length = (array.n or (#array * 4)) - (startIndex - 1) * 4
local t = {}
for i = startIndex, #array do
local uint32 = array [i]
local uint80 = uint32 % 256 uint32 = uint32 - uint80
local uint81 = uint32 % 65536 uint32 = uint32 - uint81 uint32 = uint32 * oneOver65536
local uint82 = uint32 % 256 uint32 = uint32 - uint82
local uint83 = uint32 % 65536
t [#t + 1] = byteCharacters2 [uint80 + uint81]
t [#t + 1] = byteCharacters2 [uint82 + uint83]
end
if length % 4 == 1 then
t [#t] = nil
t [#t] = string_sub (t [#t], 1, 1)
elseif length % 4 == 2 then
t [#t] = nil
elseif length % 4 == 3 then
t [#t] = string_sub (t [#t], 1, 1)
end
return table_concat (t)
end
-- Converts an array of uint32s to a string
function crypto.UInt32ArrayToString (array, startIndex)
return crypto.Int32ArrayToString (array, startIndex)
end
return crypto

View File

@@ -0,0 +1,262 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local urlobj = _G.pac_urlobj
local TIMEOUT_VALUE = CreateConVar('pac_objdl_timeout', '15', {FCVAR_ARCHIVE}, 'OBJ download timeout in seconds')
local CACHE_OBJS = CreateConVar('pac_obj_cache', '1', {FCVAR_ARCHIVE}, 'DEBUG: Cache Object files on disk. Disables disk cache access (like cache does not exist in code)')
local QUEUEITEM = {}
-- Warning: This code is concurrency hell
-- Either the decode from the cache or the decode from the web could finish first / last
-- And the web request handler will often decide to not override the cache
local function CreateQueueItem(url)
local queueItem = {}
setmetatable (queueItem, { __index = QUEUEITEM })
queueItem:Initialize (url)
return queueItem
end
function QUEUEITEM:Initialize (url)
self.Url = url
self.Data = nil
self.UsingCachedData = false
-- Cache
self.CacheDecodeFinished = false
-- Download
self.DownloadAttemptCount = 0
self.DownloadTimeoutTime = 0
self.Downloading = false
self.DownloadFinished = false
-- Status
self.Status = nil
self.Finished = false
-- Model
self.Model = nil
-- Decoding parameters
self.GenerateNormals = false
-- Callbacks
self.CallbackSet = {}
self.DownloadCallbackSet = {}
self.StatusCallbackSet = {}
end
function QUEUEITEM:GetUrl ()
return self.Url
end
-- Cache
function QUEUEITEM:BeginCacheRetrieval ()
if not CACHE_OBJS:GetBool() then return end
self.Data = urlobj.DataCache:GetItem(self.Url)
if not self.Data then return end
self.Model = urlobj.CreateModelFromObjData(self.Data, self.GenerateNormals,
function (finished, statusMessage)
if self:IsFinished () then return end
if self.DownloadFinished and not self.UsingCachedData then return end
if finished and not self.DownloadFinished then
self:SetStatus ("")
else
self:SetStatus ("Cached model: " .. statusMessage)
end
if self.DownloadFinished and self.UsingCachedData then
self:SetFinished (finished)
end
if finished then
self.CacheDecodeFinished = true
end
end
)
self:DispatchCallbacks (self.Model)
end
function QUEUEITEM:IsCacheDecodeFinished ()
return self.CacheDecodeFinished
end
-- Download
function QUEUEITEM:AbortDownload ()
self.Downloading = false
self:SetStatus ("Download aborted")
end
function QUEUEITEM:BeginDownload ()
if self:IsDownloading () then return end
self:SetStatus ("Downloading")
self.Downloading = true
self.DownloadTimeoutTime = pac.RealTime + TIMEOUT_VALUE:GetFloat()
self.DownloadAttemptCount = self.DownloadAttemptCount + 1
local function success(data)
if not self.Downloading then return end
self.Downloading = false
self.DownloadFinished = true
pac.dprint("downloaded model %q %s", self.Url, string.NiceSize(#data))
pac.dprint("%s", data)
self:DispatchDownloadCallbacks ()
self:ClearDownloadCallbacks ()
self.UsingCachedData = self.Data == data
if self.UsingCachedData then
if self.CacheDecodeFinished then
self:SetFinished (true)
end
else
self.Data = data
if CACHE_OBJS:GetBool() then
urlobj.DataCache:AddItem (self.Url, self.Data)
end
self.Model = urlobj.CreateModelFromObjData(self.Data, self.GenerateNormals,
function (finished, statusMessage)
self:SetStatus (statusMessage)
self:SetFinished (finished)
if self:IsFinished () then
self:ClearStatusCallbacks ()
end
end
)
end
self:DispatchCallbacks (self.Model)
self:ClearCallbacks ()
end
local function failure(err, fatal)
-- dont bother with servezilf he said No
if fatal then
self.DownloadAttemptCount = 100
end
self.DownloadTimeoutTime = 0
self:SetStatus ("Failed - " .. err)
end
pac.HTTPGet(self.Url, success, failure)
end
function QUEUEITEM:GetDownloadAttemptCount ()
return self.DownloadAttemptCount
end
function QUEUEITEM:IsDownloading ()
return self.Downloading
end
function QUEUEITEM:HasDownloadTimedOut ()
return self:IsDownloading () and pac.RealTime > self.DownloadTimeoutTime
end
-- Status
function QUEUEITEM:GetStatus ()
return self.Status
end
function QUEUEITEM:IsFinished ()
return self.Finished
end
function QUEUEITEM:SetStatus (status)
if self.Status == status then return self end
self.Status = status
self:DispatchStatusCallbacks (self.Finished, self.Status)
return self
end
function QUEUEITEM:SetFinished (finished)
if self.Finished == finished then return self end
self.Finished = finished
self:DispatchStatusCallbacks (self.Finished, self.Status)
return self
end
-- Model
function QUEUEITEM:GetModel ()
return self.Model
end
-- Callbacks
function QUEUEITEM:AddCallback (callback)
self.CallbackSet [callback] = true
if self.Model then
callback (self.Model)
end
end
function QUEUEITEM:AddDownloadCallback (downloadCallback)
self.DownloadCallbackSet [downloadCallback] = true
end
function QUEUEITEM:AddStatusCallback (statusCallback)
self.StatusCallbackSet [statusCallback] = true
statusCallback (self.Finished, self.Status)
end
function QUEUEITEM:ClearCallbacks ()
self.CallbackSet = {}
end
function QUEUEITEM:ClearDownloadCallbacks ()
self.DownloadCallbackSet = {}
end
function QUEUEITEM:ClearStatusCallbacks ()
self.StatusCallbackSet = {}
end
function QUEUEITEM:DispatchCallbacks (...)
for callback, _ in pairs (self.CallbackSet) do
callback (...)
end
end
function QUEUEITEM:DispatchDownloadCallbacks (...)
for downloadCallback, _ in pairs (self.DownloadCallbackSet) do
downloadCallback (...)
end
end
function QUEUEITEM:DispatchStatusCallbacks (...)
for statusCallback, _ in pairs (self.StatusCallbackSet) do
statusCallback (...)
end
end
return CreateQueueItem

View File

@@ -0,0 +1,703 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local urlobj = {}
_G.pac_urlobj = urlobj
local CreateCache = include("pac3/libraries/urlobj/cache.lua")
local CreateQueueItem = include("pac3/libraries/urlobj/queueitem.lua")
_G.pac_urlobj = nil
urlobj.DataCache = CreateCache("objcache")
local maxAgeConvar = CreateConVar("pac_obj_cache_maxage", "604800", FCVAR_ARCHIVE, "Maximum age of cache entries in seconds, default is 1 week.")
urlobj.DataCache:ClearBefore(os.time() - maxAgeConvar:GetFloat())
concommand.Add("pac_urlobj_clear_disk", function()
urlobj.DataCache:Clear()
pac.Message("Disk cache cleared")
end, nil, "Clears obj file cache on disk")
local SIMULATENOUS_DOWNLOADS = CreateConVar("pac_objdl_streams", "4", {FCVAR_ARCHIVE}, "OBJ files download streams")
local CURRENTLY_DOWNLOADING = 0
urlobj.Cache = {}
urlobj.CacheCount = 0
urlobj.Queue = {}
urlobj.QueueCount = 0
urlobj.DownloadQueue = {}
urlobj.DownloadQueueCount = 0
local pac_enable_urlobj = CreateClientConVar("pac_enable_urlobj", "1", true)
concommand.Add("pac_urlobj_clear_cache",
function ()
urlobj.ClearCache()
urlobj.ClearQueue()
end
)
function urlobj.Reload()
urlobj.ClearCache()
end
function urlobj.ClearCache()
urlobj.Cache = {}
urlobj.CacheCount = 0
end
function urlobj.ClearQueue()
urlobj.Queue = {}
urlobj.QueueCount = 0
urlobj.DownloadQueue = {}
urlobj.DownloadQueueCount = 0
CURRENTLY_DOWNLOADING = 0
end
function urlobj.GetObjFromURL(url, forceReload, generateNormals, callback, statusCallback)
if not pac_enable_urlobj:GetBool() then return end
-- if it"s already downloaded just return it
if callback and not forceReload and urlobj.Cache[url] then
callback(urlobj.Cache[url])
return
end
-- Add item to queue
if not urlobj.Queue[url] then
local queueItem = CreateQueueItem(url)
urlobj.Queue[url] = queueItem
urlobj.QueueCount = urlobj.QueueCount + 1
urlobj.DownloadQueue[url] = queueItem
urlobj.DownloadQueueCount = urlobj.DownloadQueueCount + 1
queueItem:BeginCacheRetrieval()
queueItem:AddStatusCallback(
function(finished, statusMessage)
if not finished then return end
urlobj.Queue[url] = nil
urlobj.QueueCount = urlobj.QueueCount - 1
urlobj.Cache[url] = queueItem:GetModel()
urlobj.CacheCount = urlobj.CacheCount + 1
end
)
end
-- Add callbacks
if callback then urlobj.Queue[url]:AddCallback (callback ) end
if statusCallback then
urlobj.Queue[url]:AddStatusCallback(function(isFinished, mStatus)
statusCallback(isFinished, mStatus ~= "" and mStatus or "Queued for processing")
end)
end
end
local thinkThreads = {}
local PARSING_THERSOLD = CreateConVar("pac_obj_runtime", "0.002", {FCVAR_ARCHIVE}, "Maximal parse runtime in seconds")
local PARSE_CHECK_LINES = 30
local function Think()
local PARSING_THERSOLD = PARSING_THERSOLD:GetFloat()
for i, threadData in ipairs(thinkThreads) do
local statusCallback, co = threadData.statusCallback, threadData.co
local t0 = SysTime ()
local success, finished, statusMessage, msg
while SysTime () - t0 < PARSING_THERSOLD do
success, finished, statusMessage, msg = coroutine.resume(co)
if not success then break end
if finished then break end
end
if not success then
table.remove(thinkThreads, i)
error(finished)
statusCallback(true, "Decoding error")
elseif finished then
statusCallback(true, "Finished")
table.remove(thinkThreads, i)
else
if statusMessage == "Preprocessing lines" then
statusCallback(false, statusMessage .. " " .. msg)
elseif msg then
statusCallback(false, statusMessage .. " " .. math.Round(msg*100) .. " %")
else
statusCallback(false, statusMessage)
end
end
break
end
end
pac.AddHook("Think", "parse_obj", Think)
local nextParsingHookId = 0
function urlobj.CreateModelFromObjData(objData, generateNormals, statusCallback)
local mesh = Mesh()
local co = coroutine.create (
function ()
local meshData = urlobj.ParseObj(objData, generateNormals)
mesh:BuildFromTriangles (meshData)
coroutine.yield (true)
end
)
table.insert(thinkThreads, {
objData = objData,
generateNormals = generateNormals,
statusCallback = statusCallback,
co = co,
mesh = mesh
})
statusCallback(false, "Queued")
return { mesh }
end
-- ===========================================================================
-- Everything below is internal and should only be called by code in this file
-- ===========================================================================
-- parser made by animorten
-- modified slightly by capsadmin
local ipairs = ipairs
local pairs = pairs
local tonumber = tonumber
local math_sqrt = math.sqrt
local string_gmatch = string.gmatch
local string_gsub = string.gsub
local string_match = string.match
local string_sub = string.sub
local string_Split = string.Split
local string_Trim = string.Trim
local table_concat = table.concat
local table_insert = table.insert
local Vector = Vector
local facesMapper = "([0-9]+)/?([0-9]*)/?([0-9]*)"
local numberMatch = "(-?[0-9.+-e0-9]+)"
local vMatch = "^ *v *" .. numberMatch .. " +" .. numberMatch .. " +" .. numberMatch
local vtMatch = "^ *vt *" .. numberMatch .. " +" .. numberMatch
local vnMatch = "^ *vn *" .. numberMatch .. " +" .. numberMatch .. " +" .. numberMatch
function urlobj.ParseObj(data, generateNormals)
local coroutine_yield = coroutine.running () and coroutine.yield or function () end
local positions = {}
local texCoordsU = {}
local texCoordsV = {}
local normals = {}
local triangleList = {}
local lines = {}
local faceLines = {}
local vLines = {}
local vtLines = {}
local vnLines = {}
local facesPreprocess = {}
local i = 1
local inContinuation = false
local continuationLines = nil
local defaultNormal = Vector(0, 0, -1)
for line in string_gmatch (data, "(.-)\r?\n") do
if #line > 3 then
local first = string_sub(line, 1, 1)
if first ~= "#" and first ~= "l" and first ~= "g" and first ~= "u" then
if string_sub(line, #line) == "\\" then
line = string_sub (line, 1, #line - 1)
if inContinuation then
continuationLines[#continuationLines + 1] = line
else
inContinuation = true
continuationLines = { line }
end
else
local currLine
if inContinuation then
continuationLines[#continuationLines + 1] = line
currLine = table_concat (continuationLines)
first = string_sub(currLine, 1, 1)
inContinuation = false
continuationLines = nil
else
currLine = line
end
local second = string_sub(currLine, 1, 2)
if second == "vt" then
vtLines[#vtLines + 1] = currLine
elseif second == "vn" then
vnLines[#vnLines + 1] = currLine
elseif first == "v" then
vLines[#vLines + 1] = currLine
elseif first == "f" then
facesPreprocess[#facesPreprocess + 1] = currLine
else
lines[#lines + 1] = currLine
end
end
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Preprocessing lines", i)
end
i = i + 1
end
end
end
if inContinuation then
continuationLines[#continuationLines + 1] = line
lines[#lines + 1] = table.concat (continuationLines)
inContinuation = false
continuationLines = nil
end
coroutine_yield(false, "Preprocessing lines", i)
local lineCount = #vtLines + #vnLines + #vLines + #facesPreprocess
local inverseLineCount = 1 / lineCount
local lineProcessed = 0
for i, line in ipairs(vLines) do
local x, y, z = string_match(line, vMatch)
x, y, z = tonumber(x) or 0, tonumber(y) or 0, tonumber(z) or 0
positions[#positions + 1] = Vector(x, y, z)
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
end
lineProcessed = #vLines
for i, line in ipairs(vtLines) do
local u, v = string_match(line, vtMatch)
u, v = tonumber(u) or 0, tonumber(v) or 0
local texCoordIndex = #texCoordsU + 1
texCoordsU[texCoordIndex] = u % 1
texCoordsV[texCoordIndex] = (1 - v) % 1
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing vertices", (i + lineProcessed) * inverseLineCount)
end
end
lineProcessed = #vLines + #vtLines
if not generateNormals then
for i, line in ipairs(vnLines) do
local nx, ny, nz = string_match(line, vnMatch)
if nx and ny and nz then
nx, ny, nz = tonumber(nx) or 0, tonumber(ny) or 0, tonumber(nz) or 0 -- possible / by zero
local inverseLength = 1 / math_sqrt(nx * nx + ny * ny + nz * nz)
nx, ny, nz = nx * inverseLength, ny * inverseLength, nz * inverseLength
local normal = Vector(nx, ny, nz)
normals[#normals + 1] = normal
end
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing vertices", (i + lineProcessed) * inverseLineCount)
end
end
end
lineProcessed = #vLines + #vtLines + #vnLines
for i, line in ipairs(facesPreprocess) do
local matchLine = string_match(line, "^ *f +(.*)")
if matchLine then
-- Explode line
local parts = {}
for part in string_gmatch(matchLine, "[^ ]+") do
parts[#parts + 1] = part
end
faceLines[#faceLines + 1] = parts
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing vertices", (i + lineProcessed) * inverseLineCount)
end
end
end
local lineCount = #lines
local inverseLineCount = 1 / lineCount
local i = 1
while i <= lineCount do
local processedLine = false
-- Positions: v %f %f %f [%f]
while i <= lineCount do
local line = lines[i]
local x, y, z = string_match(line, vMatch)
if not x then break end
processedLine = true
x, y, z = tonumber(x) or 0, tonumber(y) or 0, tonumber(z) or 0
positions[#positions + 1] = Vector(x, y, z)
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
i = i + 1
end
if processedLine then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
-- Texture coordinates: vt %f %f
while i <= lineCount do
local line = lines[i]
local u, v = string_match(line, vtMatch)
if not u then break end
processedLine = true
u, v = tonumber(u) or 0, tonumber(v) or 0
local texCoordIndex = #texCoordsU + 1
texCoordsU[texCoordIndex] = u % 1
texCoordsV[texCoordIndex] = (1 - v) % 1
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
i = i + 1
end
if processedLine then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
-- Normals: vn %f %f %f
while i <= lineCount do
local line = lines[i]
local nx, ny, nz = string_match(line, vnMatch)
if not nx then break end
processedLine = true
if not generateNormals then
nx, ny, nz = tonumber(nx) or 0, tonumber(ny) or 0, tonumber(nz) or 0
local inverseLength = 1 / math_sqrt(nx * nx + ny * ny + nz * nz)
nx, ny, nz = nx * inverseLength, ny * inverseLength, nz * inverseLength
local normal = Vector(nx, ny, nz)
normals[#normals + 1] = normal
end
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
i = i + 1
end
if processedLine then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
-- Faces: f %f %f %f+
while i <= lineCount do
local line = lines[i]
local matchLine = string_match(line, "^ *f +(.*)")
if not matchLine then break end
processedLine = true
-- Explode line
local parts = {}
for part in string_gmatch(matchLine, "[^ ]+") do
parts[#parts + 1] = part
end
faceLines[#faceLines + 1] = parts
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
i = i + 1
end
if processedLine then
coroutine_yield(false, "Processing vertices", i * inverseLineCount)
end
-- Something else
if not processedLine then
i = i + 1
end
end
local faceLineCount = #faceLines
local inverseFaceLineCount = 1 / faceLineCount
for i = 1, #faceLines do
local parts = faceLines [i]
if #parts >= 3 then
-- are they always integers?
local v1PositionIndex, v1TexCoordIndex, v1NormalIndex = string_match(parts[1], facesMapper)
local v3PositionIndex, v3TexCoordIndex, v3NormalIndex = string_match(parts[2], facesMapper)
v1PositionIndex, v1TexCoordIndex, v1NormalIndex = tonumber(v1PositionIndex), tonumber(v1TexCoordIndex), tonumber(v1NormalIndex)
v3PositionIndex, v3TexCoordIndex, v3NormalIndex = tonumber(v3PositionIndex), tonumber(v3TexCoordIndex), tonumber(v3NormalIndex)
for i = 3, #parts do
local v2PositionIndex, v2TexCoordIndex, v2NormalIndex = string_match(parts[i], facesMapper)
v2PositionIndex, v2TexCoordIndex, v2NormalIndex = tonumber(v2PositionIndex), tonumber(v2TexCoordIndex), tonumber(v2NormalIndex)
local v1 = { pos_index = nil, pos = nil, u = nil, v = nil, normal = nil, userdata = nil }
local v2 = { pos_index = nil, pos = nil, u = nil, v = nil, normal = nil, userdata = nil }
local v3 = { pos_index = nil, pos = nil, u = nil, v = nil, normal = nil, userdata = nil }
v1.pos_index = v1PositionIndex
v2.pos_index = v2PositionIndex
v3.pos_index = v3PositionIndex
v1.pos = positions[v1PositionIndex]
v2.pos = positions[v2PositionIndex]
v3.pos = positions[v3PositionIndex]
if #texCoordsU > 0 then
v1.u = texCoordsU[v1TexCoordIndex] or 0
v1.v = texCoordsV[v1TexCoordIndex] or 0
v2.u = texCoordsU[v2TexCoordIndex] or 0
v2.v = texCoordsV[v2TexCoordIndex] or 0
v3.u = texCoordsU[v3TexCoordIndex] or 0
v3.v = texCoordsV[v3TexCoordIndex] or 0
else
v1.u, v1.v = 0, 0
v2.u, v2.v = 0, 0
v3.u, v3.v = 0, 0
end
if #normals > 0 then
v1.normal = normals[v1NormalIndex]
v2.normal = normals[v2NormalIndex]
v3.normal = normals[v3NormalIndex]
else
v1.normal = defaultNormal
v2.normal = defaultNormal
v3.normal = defaultNormal
end
triangleList [#triangleList + 1] = v1
triangleList [#triangleList + 1] = v2
triangleList [#triangleList + 1] = v3
v3PositionIndex, v3TexCoordIndex, v3NormalIndex = v2PositionIndex, v2TexCoordIndex, v2NormalIndex
end
end
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Processing faces", i * inverseFaceLineCount)
end
end
coroutine_yield(false, "Processing faces", faceLineCount)
if generateNormals then
local vertexNormals = {}
local triangleCount = #triangleList / 3
local inverseTriangleCount = 1 / triangleCount
for i = 1, triangleCount do
local a, b, c = triangleList[1+(i-1)*3+0], triangleList[1+(i-1)*3+1], triangleList[1+(i-1)*3+2]
local normal = (c.pos - a.pos):Cross(b.pos - a.pos):GetNormalized()
vertexNormals[a.pos_index] = vertexNormals[a.pos_index] or Vector()
vertexNormals[a.pos_index] = (vertexNormals[a.pos_index] + normal)
vertexNormals[b.pos_index] = vertexNormals[b.pos_index] or Vector()
vertexNormals[b.pos_index] = (vertexNormals[b.pos_index] + normal)
vertexNormals[c.pos_index] = vertexNormals[c.pos_index] or Vector()
vertexNormals[c.pos_index] = (vertexNormals[c.pos_index] + normal)
if i % PARSE_CHECK_LINES == 0 then
coroutine_yield(false, "Generating normals", i * inverseTriangleCount)
end
end
coroutine_yield(false, "Generating normals", triangleCount)
local vertexCount = #triangleList
local inverseVertexCount = 1 / vertexCount
for i = 1, vertexCount do
local normal = vertexNormals[triangleList[i].pos_index] or defaultNormal
normal:Normalize()
normals[i] = normal
triangleList[i].normal = normal
coroutine_yield(false, "Normalizing normals", i * inverseVertexCount)
end
end
do
-- Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software, 2001. http://terathon.com/code/tangent.html
local tan1 = {}
local tan2 = {}
local vertexCount = #triangleList
for i = 1, vertexCount do
tan1[i] = Vector(0, 0, 0)
tan2[i] = Vector(0, 0, 0)
end
for i = 1, vertexCount - 2, 3 do
local vert1, vert2, vert3 = triangleList[i], triangleList[i+1], triangleList[i+2]
local p1, p2, p3 = vert1.pos, vert2.pos, vert3.pos
local u1, u2, u3 = vert1.u, vert2.u, vert3.u
local v1, v2, v3 = vert1.v, vert2.v, vert3.v
local x1 = p2.x - p1.x;
local x2 = p3.x - p1.x;
local y1 = p2.y - p1.y;
local y2 = p3.y - p1.y;
local z1 = p2.z - p1.z;
local z2 = p3.z - p1.z;
local s1 = u2 - u1;
local s2 = u3 - u1;
local t1 = v2 - v1;
local t2 = v3 - v1;
local r = 1 / (s1 * t2 - s2 * t1)
local sdir = Vector((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
local tdir = Vector((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
tan1[i]:Add(sdir)
tan1[i+1]:Add(sdir)
tan1[i+2]:Add(sdir)
tan2[i]:Add(tdir)
tan2[i+1]:Add(tdir)
tan2[i+2]:Add(tdir)
end
local tangent = {}
for i = 1, vertexCount do
local n = triangleList[i].normal
local t = tan1[i]
local tan = (t - n * n:Dot(t))
tan:Normalize()
local w = (n:Cross(t)):Dot(tan2[i]) < 0 and -1 or 1
triangleList[i].userdata = {tan[1], tan[2], tan[3], w}
end
end
return triangleList
end
-- Download queuing
function urlobj.DownloadQueueThink()
if pac.urltex and pac.urltex.Busy then return end
for url, queueItem in pairs(urlobj.DownloadQueue) do
if not queueItem:IsDownloading() and
not queueItem:IsCacheDecodeFinished () then
queueItem:SetStatus("Queued for download (" .. urlobj.DownloadQueueCount .. " items in queue)")
end
-- Check for download timeout
if queueItem:IsDownloading() and
queueItem:HasDownloadTimedOut() then
pac.dprint("model download timed out for the %s time %q", queueItem:GetDownloadAttemptCount(), queueItem:GetUrl())
queueItem:AbortDownload()
if queueItem:GetDownloadAttemptCount() > 3 then
-- Give up
urlobj.Queue[url] = nil
urlobj.QueueCount = urlobj.QueueCount - 1
urlobj.DownloadQueue[url] = nil
urlobj.DownloadQueueCount = urlobj.DownloadQueueCount - 1
CURRENTLY_DOWNLOADING = CURRENTLY_DOWNLOADING - 1
pac.dprint("model download timed out for good %q", url)
else
-- Reattempt download
queueItem:BeginDownload()
end
end
end
urlobj.Busy = next (urlobj.Queue) ~= nil
if CURRENTLY_DOWNLOADING >= SIMULATENOUS_DOWNLOADS:GetInt() then return end
-- Start download of next item in queue
if next(urlobj.DownloadQueue) then
for url, queueItem in pairs(urlobj.DownloadQueue) do
if not queueItem:IsDownloading() then
queueItem:BeginDownload()
queueItem:AddDownloadCallback(
function()
urlobj.DownloadQueue[url] = nil
urlobj.DownloadQueueCount = urlobj.DownloadQueueCount - 1
CURRENTLY_DOWNLOADING = CURRENTLY_DOWNLOADING - 1
end
)
CURRENTLY_DOWNLOADING = CURRENTLY_DOWNLOADING + 1
pac.dprint("requesting model download %q", url)
if CURRENTLY_DOWNLOADING >= SIMULATENOUS_DOWNLOADS:GetInt() then return end
end
end
end
end
timer.Create("urlobj_download_queue", 0.1, 0, urlobj.DownloadQueueThink)
return urlobj

View File

@@ -0,0 +1,280 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local urltex = {}
urltex.TextureSize = 1024
urltex.ActivePanel = urltex.ActivePanel or NULL
urltex.Queue = urltex.Queue or {}
urltex.Cache = urltex.Cache or {}
concommand.Add("pac_urltex_clear_cache", function()
urltex.Cache = {}
urltex.Queue = {}
end)
if urltex.ActivePanel:IsValid() then
urltex.ActivePanel:Remove()
end
local enable = CreateClientConVar("pac_enable_urltex", "1", true)
local EMPTY_FUNC = function() end
local function findFlag(url, flagID)
local startPos, endPos = url:find(flagID)
if not startPos then return url, false end
if url:sub(endPos + 1, endPos + 1) == ' ' or url:sub(startPos - 1, startPos - 1) == ' ' then
url = url:gsub(' ?' .. flagID .. ' ?', '')
return url, true
end
return url, false
end
function urltex.GetMaterialFromURL(url, callback, skip_cache, shader, size, size_hack, additionalData)
if size_hack == nil then
size_hack = true
end
additionalData = additionalData or {}
shader = shader or "VertexLitGeneric"
if not enable:GetBool() then return end
local noclampS, noclamp, noclampT
url, noclampS = findFlag(url, 'noclamps')
url, noclampT = findFlag(url, 'noclampt')
url, noclamp = findFlag(url, 'noclamp')
local urlAddress = url
local urlIndex = url
if noclamp then
urlIndex = urlIndex .. ' noclamp'
elseif noclampS then
urlIndex = urlIndex .. ' noclampS'
elseif noclampT then
urlIndex = urlIndex .. ' noclampT'
end
noclamp = noclamp or noclampS and noclampT
local renderTargetNeeded =
noclampS or
noclamp or
noclampT
if isfunction(callback) and not skip_cache and urltex.Cache[urlIndex] then
local tex = urltex.Cache[urlIndex]
local mat = CreateMaterial("pac3_urltex_" .. pac.Hash(), shader, additionalData)
mat:SetTexture("$basetexture", tex)
callback(mat, tex)
return
end
callback = callback or EMPTY_FUNC
if urltex.Queue[urlIndex] then
table.insert(urltex.Queue[urlIndex].callbacks, callback)
else
urltex.Queue[urlIndex] = {
url = urlAddress,
urlIndex = urlIndex,
callbacks = {callback},
tries = 0,
size = size,
size_hack = size_hack,
shader = shader,
noclampS = noclampS,
noclampT = noclampT,
noclamp = noclamp,
rt = renderTargetNeeded,
additionalData = additionalData
}
end
end
function urltex.Think()
if not pac.IsEnabled() then return end
if table.Count(urltex.Queue) > 0 then
for url, data in pairs(urltex.Queue) do
-- when the panel is gone start a new one
if not urltex.ActivePanel:IsValid() then
urltex.StartDownload(data.url, data)
end
end
urltex.Busy = true
else
urltex.Busy = false
end
end
timer.Create("urltex_queue", 0.1, 0, urltex.Think)
function urltex.StartDownload(url, data)
if urltex.ActivePanel:IsValid() then
urltex.ActivePanel:Remove()
end
url = pac.FixUrl(url)
local size = tonumber(data.size or urltex.TextureSize)
local id = "urltex_download_" .. url
local pnl
local frames_passed = 0
local function createDownloadPanel()
pnl = vgui.Create("DHTML")
-- Tested in PPM/2, this code works perfectly
pnl:SetVisible(false)
pnl:SetSize(size, size)
pnl:SetHTML([[<html>
<head>
<style type="text/css">
html
{
overflow:hidden;
]] .. (data.size_hack and "margin: -8px -8px;" or "margin: 0px 0px;") .. [[
}
</style>
<script>
window.onload = function() {
setInterval(function() {
console.log('REAL_FRAME_PASSED');
}, 50);
};
</script>
</head>
<body>
<img src="]] .. url .. [[" alt="" width="]] .. size .. [[" height="]] .. size .. [[" />
</body>
</html>]])
pnl:Refresh()
function pnl:ConsoleMessage(msg)
if msg == 'REAL_FRAME_PASSED' then
frames_passed = frames_passed + 1
end
end
urltex.ActivePanel = pnl
end
local go = false
local time = 0
local timeoutNum = 0
local think
local function onTimeout()
timeoutNum = timeoutNum + 1
if IsValid(pnl) then pnl:Remove() end
if timeoutNum < 5 then
pac.dprint("material download %q timed out.. trying again for the %ith time", url, timeoutNum)
-- try again
go = false
time = 0
createDownloadPanel()
else
pac.dprint("material download %q timed out for good", url, timeoutNum)
pac.RemoveHook("Think", id)
timer.Remove(id)
urltex.Queue[data.urlIndex] = nil
end
end
function think()
-- panel is no longer valid
if not pnl:IsValid() then
onTimeout()
return
end
-- give it some time.. IsLoading is sometimes lying, especially on chromium branch
if not go and not pnl:IsLoading() then
time = pac.RealTime + 1
go = true
end
if go and time < pac.RealTime and frames_passed > 20 then
pnl:UpdateHTMLTexture()
local html_mat = pnl:GetHTMLMaterial()
if html_mat then
local crc = pac.Hash()
local vertex_mat = CreateMaterial("pac3_urltex_" .. crc, data.shader, data.additionalData)
local tex = html_mat:GetTexture("$basetexture")
tex:Download()
vertex_mat:SetTexture("$basetexture", tex)
-- tex:Download()
urltex.Cache[data.urlIndex] = tex
pac.RemoveHook("Think", id)
timer.Remove(id)
urltex.Queue[data.urlIndex] = nil
local rt
if data.rt then
local textureFlags = 0
textureFlags = textureFlags + 4 -- clamp S
textureFlags = textureFlags + 8 -- clamp T
textureFlags = textureFlags + 16 -- anisotropic
textureFlags = textureFlags + 256 -- no mipmaps
-- textureFlags = textureFlags + 2048 -- Texture is procedural
textureFlags = textureFlags + 32768 -- Texture is a render target
-- textureFlags = textureFlags + 67108864 -- Usable as a vertex texture
if data.noclamp then
textureFlags = textureFlags - 4
textureFlags = textureFlags - 8
elseif data.noclampS then
textureFlags = textureFlags - 4
elseif data.noclampT then
textureFlags = textureFlags - 8
end
local vertex_mat2 = CreateMaterial("pac3_urltex_" .. crc .. '_hack', 'UnlitGeneric', data.additionalData)
vertex_mat2:SetTexture("$basetexture", tex)
rt = GetRenderTargetEx("pac3_urltex_" .. crc, size, size, RT_SIZE_NO_CHANGE, MATERIAL_RT_DEPTH_NONE, textureFlags, CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGB888)
render.PushRenderTarget(rt)
render.Clear(0, 0, 0, 255, false, false)
cam.Start2D()
surface.SetMaterial(vertex_mat2)
surface.SetDrawColor(255, 255, 255)
surface.DrawTexturedRect(0, 0, size, size)
cam.End2D()
render.PopRenderTarget()
vertex_mat:SetTexture('$basetexture', rt)
urltex.Cache[data.urlIndex] = rt
end
timer.Simple(0, function()
pnl:Remove()
end)
if data.callbacks then
for i, callback in pairs(data.callbacks) do
callback(vertex_mat, rt or tex)
end
end
end
end
end
pac.AddHook("Think", id, think)
-- 5 sec max timeout, 5 maximal timeouts
timer.Create(id, 5, 5, onTimeout)
createDownloadPanel()
end
return urltex

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,436 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- made by Morten and CapsAdmin
pac.webaudio = pac.webaudio or {}
local webaudio = pac.webaudio
webaudio.Browser = webaudio.Browser or {}
webaudio.Browser.States =
{
Uninitialized = 0,
Initializing = 1,
Initialized = 2
}
if webaudio.Browser.Control and
webaudio.Browser.Control:IsValid () then
webaudio.Browser.Control:Remove ()
webaudio.Browser.Control = nil
end
webaudio.Browser.State = webaudio.Browser.States.Uninitialized
webaudio.Browser.Control = nil
webaudio.Browser.JavascriptQueue = {}
webaudio.Browser.Volume = nil
local CONSOLE_MESSAGE_PREFIX = Color(121, 221, 203)
local CONSOLE_MESSAGE_COLOR = Color(200, 200, 200)
function webaudio.Browser.Initialize()
if webaudio.Browser.State ~= webaudio.Browser.States.Uninitialized then return end
webaudio.Browser.State = webaudio.Browser.States.Initializing
if webaudio.Browser.Control then webaudio.Browser.Control:Remove() end
webaudio.Browser.Control = vgui.Create("DHTML")
webaudio.Browser.Control:SetVisible(false)
webaudio.Browser.Control:SetPos(ScrW(), ScrH())
webaudio.Browser.Control:SetSize(1, 1)
local lastMessage = nil
webaudio.Browser.Control.ConsoleMessage = function(self, message)
-- why does awesomium crash in the first place?
if msg == "Uncaught ReferenceError: lua is not defined" then
webaudio.Browser.State = webaudio.Browser.States.Uninitialized
end
if lastMessage ~= message then
lastMessage = message
pac.Message(CONSOLE_MESSAGE_PREFIX, '[OGG] ', CONSOLE_MESSAGE_COLOR, unpack(pac.RepackMessage(message)))
end
end
webaudio.Browser.Control:AddFunction("lua", "print", webaudio.DebugPrint)
webaudio.Browser.Control:AddFunction("lua", "message", function(messageType, ...)
local args = {...}
webaudio.DebugPrint(messageType, ...)
if messageType == "initialized" then
webaudio.Browser.State = webaudio.Browser.States.Initialized
webaudio.SampleRate = args[1]
elseif messageType == "stream" then
local stream = webaudio.Streams.GetStream(tonumber(args[2]) or 0)
if not stream then return end
local messageType = args[1]
stream:HandleBrowserMessage(messageType, unpack(args, 3, table.maxn(args)))
end
end)
--webaudio.Browser.Control:OpenURL("asset://garrysmod/lua/pac3/core/client/libraries/urlogg.lua")
webaudio.Browser.Control:SetHTML(webaudio.Browser.HTML)
end
function webaudio.Browser.IsInitializing()
return webaudio.Browser.State == webaudio.Browser.States.Initializing
end
function webaudio.Browser.IsInitialized()
return webaudio.Browser.State == webaudio.Browser.States.Initialized
end
-- Javascript
function webaudio.Browser.RunJavascript(code)
if IsValid(webaudio.Browser.Control) then webaudio.Browser.Control:QueueJavascript(code) end
end
function webaudio.Browser.QueueJavascript(code)
webaudio.Browser.JavascriptQueue [#webaudio.Browser.JavascriptQueue + 1] = code
end
function webaudio.Browser.Think()
if #webaudio.Browser.JavascriptQueue == 0 then return end
local code = table.concat(webaudio.Browser.JavascriptQueue, "\n")
webaudio.Browser.RunJavascript(code)
webaudio.Browser.JavascriptQueue = {}
end
-- Audio
function webaudio.Browser.SetVolume (volumeFraction)
if webaudio.Browser.Volume == volumeFraction then return end
webaudio.Browser.Volume = volumeFraction
webaudio.Browser.QueueJavascript(string.format("gain.gain.value = %f", volumeFraction))
end
webaudio.Browser.HTML = [==[
<script>
window.onerror = function(description, url, line)
{
lua.print("Unhandled exception at line " + line + ": " + description)
}
function lerp(x0, x1, t)
{
return x0 * (1 - t) + x1 * t;
}
var audio, gain, processor, analyser, streams = [];
function open()
{
if(audio)
{
audio.destination.disconnect();
delete audio;
delete gain;
}
if(typeof AudioContext != "undefined"){
audio = new AudioContext;
processor = audio.createScriptProcessor(4096, 0, 2);
gain = audio.createGain();
}else{
audio = new webkitAudioContext;
processor = audio.createJavaScriptNode(4096, 0, 1);
gain = audio.createGainNode();
}
processor.onaudioprocess = function(event)
{
var outputLeft = event.outputBuffer.getChannelData(0);
var outputRight = event.outputBuffer.getChannelData(1);
for(var i = 0; i < event.outputBuffer.length; ++i)
{
outputLeft [i] = 0;
outputRight[i] = 0;
}
for(var i in streams)
{
var stream = streams[i];
if(!stream.use_echo && (stream.paused || (stream.vol_left < 0.001 && stream.vol_right < 0.001)))
{
continue;
}
var echol;
var echor;
if (stream.use_echo && stream.echo_buffer)
{
echol = stream.echo_buffer.getChannelData(0);
echor = stream.echo_buffer.getChannelData(1);
}
var inputLength = stream.buffer.length;
var inputLeft = stream.buffer.getChannelData(0);
var inputRight = stream.buffer.numberOfChannels == 1 ? inputLeft : stream.buffer.getChannelData(1);
var sml = 0;
var smr = 0;
for(var j = 0; j < event.outputBuffer.length; ++j)
{
if (stream.use_smoothing)
{
stream.speed_smooth = stream.speed_smooth + (stream.speed - stream.speed_smooth) * 0.001;
stream.vol_left_smooth = stream.vol_left_smooth + (stream.vol_left - stream.vol_left_smooth ) * 0.001;
stream.vol_right_smooth = stream.vol_right_smooth + (stream.vol_right - stream.vol_right_smooth) * 0.001;
}
else
{
stream.speed_smooth = stream.speed;
stream.vol_left_smooth = stream.vol_left_smooth;
stream.vol_right_smooth = stream.vol_right_smooth;
}
if (stream.paused || stream.max_loop > 0 && stream.position > inputLength * stream.max_loop)
{
stream.done_playing = true;
if (!stream.paused)
{
lua.message("stream", "stop", stream.id);
stream.paused = true;
}
if (!stream.use_echo)
{
break;
}
}
else
{
stream.done_playing = false;
}
var index = (stream.position >> 0) % inputLength;
var echo_index = (stream.position >> 0) % stream.echo_delay;
var left = 0;
var right = 0;
if (!stream.done_playing)
{
// filters
if (stream.filter_type == 0)
{
// None
left = inputLeft [index] * stream.vol_left_smooth;
right = inputRight[index] * stream.vol_right_smooth;
}
else
{
sml = sml + (inputLeft [index] - sml) * stream.filter_fraction;
smr = smr + (inputRight[index] - smr) * stream.filter_fraction;
if (stream.filter_type == 1)
{
// Low pass
left = sml * stream.vol_left_smooth;
right = smr * stream.vol_right_smooth;
}
else if (stream.filter_type == 2)
{
// High pass
left = (inputLeft [index] - sml) * stream.vol_left_smooth;
right = (inputRight[index] - smr) * stream.vol_right_smooth;
}
}
}
if (stream.use_echo)
{
echol[echo_index] = echol[echo_index] * stream.echo_feedback + left;
echor[echo_index] = echor[echo_index] * stream.echo_feedback + right;
outputLeft [j] += echol[echo_index];
outputRight[j] += echor[echo_index];
}
else
{
outputLeft [j] += left;
outputRight[j] += right;
}
stream.position += stream.speed_smooth;
}
}
};
processor.connect(gain)
gain.connect(audio.destination)
lua.message("initialized", audio.sampleRate)
}
function close()
{
if(audio)
{
audio.destination.disconnect();
delete audio
audio = null
lua.message("uninitialized")
}
}
var buffer_cache = []
function download_buffer(url, callback, skip_cache, id)
{
if (!skip_cache && buffer_cache[url])
{
callback(buffer_cache[url])
return
}
var request = new XMLHttpRequest
request.open("GET", url)
request.responseType = "arraybuffer"
request.send()
request.onload = function()
{
lua.print("decoding " + url + " " + request.response.byteLength + " ...")
audio.decodeAudioData(request.response,
function(buffer)
{
lua.print("decoded " + url + " successfully")
callback(buffer)
buffer_cache[url] = buffer
},
function(err)
{
lua.print("decoding error " + url + " " + err)
lua.message("stream", "call", id, "OnError", "decoding failed", err)
}
)
}
request.onprogress = function(event)
{
lua.print("downloading " + (event.loaded / event.total) * 100)
}
request.onerror = function()
{
lua.print("downloading " + url + " errored")
lua.message("stream", "call", id, "OnError", "download failed")
};
}
function createStream(url, id, skip_cache)
{
lua.print("Loading " + url)
download_buffer(url, function(buffer)
{
var stream = {}
stream.id = id
stream.position = 0
stream.buffer = buffer
stream.url = url
stream.speed = 1 // 1 = normal pitch
stream.max_loop = 1 // -1 = inf
stream.vol_left = 1
stream.vol_right = 1
stream.filter = audio.createBiquadFilter();
stream.paused = true
stream.use_smoothing = true
stream.echo_volume = 0
stream.filter_type = 0
stream.filter_fraction = 1
stream.done_playing = false
stream.use_echo = false
stream.echo_feedback = 0.75
stream.echo_buffer = false
stream.vol_left_smooth = 0
stream.vol_right_smooth = 0
stream.speed_smooth = stream.speed
stream.play = function(stop, position)
{
if(position !== undefined)
{
stream.position = position
}
stream.paused = !stop
};
stream.usefft = function(enable)
{
// later
}
stream.useEcho = function(b) {
stream.use_echo = b
if (b)
{
stream.setEchoDelay(stream.echo_delay)
}
else
{
stream.echo_buffer = undefined
}
}
stream.setEchoDelay = function(x) {
if(stream.use_echo && (!stream.echo_buffer || (x != stream.echo_buffer.length))) {
var size = 1;
while((size <<= 1) < x);
stream.echo_buffer = audio.createBuffer(2, size, audio.sampleRate);
}
stream.echo_delay = x;
}
streams[id] = stream
lua.message("stream", "loaded", id, buffer.length)
}, skip_cache, id)
}
function destroyStream(id)
{
}
open()
</script>
]==]

View File

@@ -0,0 +1,473 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- made by Morten and CapsAdmin
pac.webaudio = pac.webaudio or {}
local webaudio = pac.webaudio
webaudio.Streams = webaudio.Streams or {}
webaudio.FilterType =
{
None = 0,
LowPass = 1,
HighPass = 2,
}
local listenerPosition, listenerAngle, listenerVelocity = Vector(), Angle(), Vector()
local lastListenerPosition, lastListenerPositionTime
pac.AddHook("RenderScene", "webaudio_3d", function(position, angle)
listenerPosition = position
listenerAngle = angle
lastListenerPosition = lastListenerPosition or listenerPosition
lastListenerPositionTime = lastListenerPositionTime or (CurTime() - FrameTime())
listenerVelocity = (listenerPosition - lastListenerPosition) / (CurTime() - lastListenerPositionTime)
lastListenerPosition = listenerPosition
lastListenerPositionTime = CurTime()
end)
webaudio.Streams.STREAM = {}
local STREAM = webaudio.Streams.STREAM
STREAM.__index = STREAM
-- Identity
STREAM.Id = nil
STREAM.Url = "" -- ??
-- State
STREAM.Loaded = false
-- Audio
STREAM.SampleCount = 0
-- Playback
STREAM.Paused = true
STREAM.SamplePosition = 0
STREAM.MaxLoopCount = nil
-- Playback speed
STREAM.PlaybackSpeed = 1
STREAM.AdditivePitchModifier = 0
-- Volume
STREAM.Panning = 0
STREAM.Volume = 1
STREAM.AdditiveVolumeFraction = 0
-- 3d
STREAM.Use3d = nil
STREAM.UseDoppler = true
STREAM.SourceEntity = NULL
STREAM.SourcePosition = nil
STREAM.LastSourcePosition = nil
STREAM.LastSourcePositionTime = nil
STREAM.SourceVelocity = nil
STREAM.SourceRadius = 1000
STREAM.ListenerOutOfRadius = false
local function DECLARE_PROPERTY(propertyName, javascriptSetterCode, defaultValue, filterFunction)
STREAM[propertyName] = defaultValue
STREAM["Set" .. propertyName] = function(self, value)
if filterFunction then
value = filterFunction(value, self)
end
self[propertyName] = value
self:Call(javascriptSetterCode, value)
end
STREAM["Get" .. propertyName] = function(self, ...)
return self[propertyName]
end
end
-- Identity
function STREAM:GetId()
return self.Id
end
function STREAM:SetId(id)
self.Id = id
return self
end
function STREAM:GetUrl()
return self.Url
end
function STREAM:SetUrl(url)
self.Url = url
return self
end
-- State
function STREAM:IsLoaded()
return self.Loaded
end
function STREAM:IsValid()
return true
end
function STREAM:Remove()
self.IsValid = function() return false end
end
-- Browser
function STREAM:Call(fmt, ...)
if not self.Loaded then return end
local code = string.format("try { streams[%d]%s } catch(e) { lua.print(e.toString()) }", self:GetId(), string.format(fmt, ...))
webaudio.Browser.QueueJavascript(code)
end
function STREAM:CallNow(fmt, ...)
if not self.Loaded then return end
local code = string.format("try { streams[%d]%s } catch(e) { lua.print(e.toString()) }", self:GetId(), string.format(fmt, ...))
webaudio.Browser.RunJavascript(code)
end
function STREAM:HandleBrowserMessage(messageType, ...)
if messageType == "call" then
self:HandleCallBrowserMessage(...)
elseif messageType == "fft" then
self:HandleFFTBrowserMessage(...)
elseif messageType == "stop" then
self.Paused = true
elseif messageType == "return" then
self.ReturnedValues = {...}
elseif messageType == "loaded" then
self:HandleLoadedBrowserMessage(...)
elseif t == "position" then
self:HandlePositionBrowserMessage(...)
end
end
-- Playback
function STREAM:GetMaxLoopCount()
return self.MaxLoopCount
end
function STREAM:SetMaxLoopCount(maxLoopCount)
self:Call(".max_loop = %i", maxLoopCount == true and -1 or maxLoopCount == false and 1 or tonumber(maxLoopCount) or 1)
self.MaxLoopCount = maxLoopCount
end
STREAM.SetLooping = STREAM.SetMaxLoopCount
function STREAM:GetSampleCount()
return self.SampleCount
end
function STREAM:Pause()
self.Paused = true
self:CallNow(".play(false)")
end
function STREAM:Resume()
self.Paused = false
self:UpdatePlaybackSpeed()
self:UpdateVolume()
self:CallNow(".play(true)")
end
function STREAM:Start()
self.Paused = false
self:UpdatePlaybackSpeed()
self:UpdateVolume()
self:CallNow(".play(true, 0)")
end
STREAM.Play = STREAM.Start
function STREAM:Stop()
self.Paused = true
self:CallNow(".play(false, 0)")
end
function STREAM:Restart()
self:SetSamplePosition(0)
end
function STREAM:SetPosition(positionFraction)
self:SetSamplePosition((positionFraction % 1) * self:GetSampleCount())
end
DECLARE_PROPERTY("SamplePosition", ".position = %f", 0)
-- Playback speed
function STREAM:GetPlaybackSpeed()
return self.PlaybackSpeed
end
function STREAM:GetAdditivePitchModifier()
return self.AdditivePitchModifier
end
function STREAM:SetPlaybackSpeed(playbackSpeedMultiplier)
if self.PlaybackSpeed == playbackSpeedMultiplier then return self end
self.PlaybackSpeed = playbackSpeedMultiplier
self:UpdatePlaybackSpeed()
return self
end
function STREAM:SetAdditivePitchModifier(additivePitchModifier)
if self.AdditivePitchModifier == additivePitchModifier then return self end
self.AdditivePitchModifier = additivePitchModifier
self:UpdatePlaybackSpeed()
return self
end
function STREAM:UpdatePlaybackSpeed()
self:Call(".speed = %f", self.PlaybackSpeed + self.AdditivePitchModifier)
end
-- Volume
function STREAM:GetPanning()
return self.Panning
end
function STREAM:GetVolume()
return self.Volume
end
function STREAM:GetAdditiveVolumeModifier()
return self.AdditiveVolumeModifier
end
function STREAM:SetPanning(panning)
if self.Panning == panning then return self end
self.Panning = panning
self:UpdateVolume()
return self
end
function STREAM:SetVolume(volumeFraction)
if self.Volume == volumeFraction then return self end
self.Volume = volumeFraction
self:UpdateVolume()
return self
end
function STREAM:SetAdditiveVolumeModifier (additiveVolumeFraction)
if self.AdditiveVolumeFraction == additiveVolumeFraction then return self end
self.AdditiveVolumeFraction = additiveVolumeFraction
self:UpdateVolume()
return self
end
function STREAM:UpdateSourcePosition()
if not self.SourceEntity:IsValid() then return end
self.SourcePosition = self.SourceEntity:GetPos()
end
function STREAM:UpdateVolume()
if self.Use3d then
self:UpdateVolume3d()
else
self:UpdateVolumeFlat()
end
end
function STREAM:UpdateVolumeFlat()
self:Call(".vol_right = %f", (math.Clamp(1 + self.Panning, 0, 1) * self.Volume) + self.AdditiveVolumeFraction)
self:Call(".vol_left = %f", (math.Clamp(1 - self.Panning, 0, 1) * self.Volume) + self.AdditiveVolumeFraction)
end
function STREAM:UpdateVolume3d()
self:UpdateSourcePosition()
self.SourcePosition = self.SourcePosition or Vector()
self.LastSourcePosition = self.LastSourcePosition or self.SourcePosition
self.LastSourcePositionTime = self.LastSourcePositionTime or (CurTime() - FrameTime())
self.SourceVelocity = (self.SourcePosition - self.LastSourcePosition) / (CurTime() - self.LastSourcePositionTime)
self.LastSourcePosition = self.SourcePosition
self.LastSourcePositionTime = CurTime()
local relativeSourcePosition = self.SourcePosition - listenerPosition
local distanceToSource = relativeSourcePosition:Length()
if distanceToSource < self.SourceRadius then
local pan = relativeSourcePosition:GetNormalized():Dot(listenerAngle:Right())
local volumeFraction = math.Clamp(1 - distanceToSource / self.SourceRadius, 0, 1) ^ 1.5
volumeFraction = volumeFraction * 0.75 * self.Volume
self:Call(".vol_right = %f", (math.Clamp(1 + pan, 0, 1) * volumeFraction) + self.AdditiveVolumeFraction)
self:Call(".vol_left = %f", (math.Clamp(1 - pan, 0, 1) * volumeFraction) + self.AdditiveVolumeFraction)
if self.UseDoppler then
local relativeSourcePosition = self.SourcePosition - listenerPosition
local relativeSourceVelocity = self.SourceVelocity - listenerVelocity
local relativeSourceSpeed = relativeSourcePosition:GetNormalized():Dot(-relativeSourceVelocity) * 0.0254
self:Call(".speed = %f", (self.PlaybackSpeed + (relativeSourceSpeed / webaudio.SpeedOfSound)) + self.AdditivePitchModifier)
end
self.ListenerOutOfRadius = false
else
if not self.ListenerOutOfRadius then
self:Call(".vol_right = 0")
self:Call(".vol_left = 0")
self.ListenerOutOfRadius = true
end
end
end
-- Filtering
DECLARE_PROPERTY("FilterType", ".filter_type = %i")
DECLARE_PROPERTY("FilterFraction", ".filter_fraction = %f", 0, function(num) return math.Clamp(num, 0, 1) end)
DECLARE_PROPERTY("Echo", ".useEcho(%s)", false)
DECLARE_PROPERTY("EchoDelay", ".setEchoDelay(Math.ceil(audio.sampleRate * %f))", 1, function(num) return math.max(num, 0) end)
DECLARE_PROPERTY("EchoFeedback", ".echo_feedback = %f", 0.75)
-- 3D
function STREAM:Enable3D(b)
self.Use3d = b
end
function STREAM:EnableDoppler(b)
self.UseDoppler = b
end
function STREAM:GetSourceEntity()
return self.SourceEntity
end
function STREAM:SetSourceEntity(sourceEntity, doNotRemove)
self.SourceEntity = sourceEntity
if not doNotRemove then
sourceEntity:CallOnRemove("webaudio_remove_stream_" .. tostring(self), function()
if self:IsValid() then
self:Remove()
end
end)
end
end
function STREAM:GetSourcePosition()
return self.SourcePosition
end
function STREAM:SetSourcePosition(vec)
self.SourcePosition = vec
end
function STREAM:GetSourceVelocity()
return self.SourceVelocity
end
function STREAM:SetSourceVelocity(vec)
self.SourceVelocity = vec
end
function STREAM:GetSourceRadius()
return self.SourceRadius
end
function STREAM:SetSourceRadius(sourceRadius)
self.SourceRadius = sourceRadius
end
function STREAM:Think()
if self.Paused then return end
if self.Use3d then
self:UpdateVolume3d() -- updates source position internally
else
self:UpdateSourcePosition()
end
end
function STREAM:__newindex(key, val)
if key == "OnFFT" then
if isfunction(val) then
self:Call(".usefft(true)")
else
self:Call(".usefft(false)")
end
end
rawset(self, key, val)
end
function STREAM:__tostring()
return string.format("stream[%p][%d][%s]", self, self:GetId(), self:GetUrl())
end
-- Internal browser message handlers
function STREAM:HandleCallBrowserMessage(methodName, ...)
if not self[methodName] then return end
self[methodName](self, ...)
end
function STREAM:HandleFFTBrowserMessage(serializeFFTData)
local fftArray = CompileString(serializeFFTData, "stream_fft_data")()
self.OnFFT(fftArray)
end
function STREAM:HandleLoadedBrowserMessage(sampleCount)
self.Loaded = true
self.SampleCount = sampleCount
self:SetFilterType(0)
if not self.Paused then
-- self:Play()
end
self:SetMaxLoopCount(self:GetMaxLoopCount())
self:SetEcho (self:GetEcho ())
self:SetEchoFeedback(self:GetEchoFeedback())
self:SetEchoDelay (self:GetEchoDelay ())
if self.OnLoad then
self:OnLoad()
end
end
function STREAM:HandlePositionBrowserMessage(samplePosition)
self.SamplePosition = samplePosition
end

View File

@@ -0,0 +1,61 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- made by Morten and CapsAdmin
pac.webaudio = pac.webaudio or {}
local webaudio = pac.webaudio
webaudio.Streams = webaudio.Streams or {}
webaudio.Streams.LastStreamId = 0
webaudio.Streams.Streams = {}
function webaudio.Streams.CreateStream(url)
--url = url:gsub("http[s?]://", "http://")
if not url:find("http",1,true) then
url = "asset://garrysmod/sound/" .. url
end
local stream = setmetatable({}, webaudio.Streams.STREAM)
webaudio.Streams.LastStreamId = webaudio.Streams.LastStreamId + 1
stream:SetId(webaudio.Streams.LastStreamId)
stream:SetUrl(url)
webaudio.Streams.Streams[stream:GetId()] = stream
webaudio.Browser.QueueJavascript(string.format("createStream(%q, %d)", stream:GetUrl(), stream:GetId()))
return stream
end
function webaudio.Streams.GetStream(streamId)
return webaudio.Streams.Streams[streamId]
end
function webaudio.Streams.StreamExists(streamId)
return webaudio.Streams.Streams[streamId] ~= nil
end
function webaudio.Streams.Think()
for streamId, stream in pairs(webaudio.Streams.Streams) do
if stream:IsValid() then
stream:Think()
else
stream:Stop()
webaudio.Streams.Streams[streamId] = nil
webaudio.Browser.QueueJavascript(string.format("destroyStream(%i)", stream:GetId()))
setmetatable(stream, getmetatable(NULL))
end
end
end

View File

@@ -0,0 +1,68 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- made by Morten and CapsAdmin
pac.webaudio = pac.webaudio or {}
local webaudio = pac.webaudio
webaudio.Debug = 0
webaudio.SampleRate = nil
webaudio.SpeedOfSound = 340.29 -- metres per second
function webaudio.DebugPrint(str, ...)
if webaudio.Debug == 0 then return end
if webaudio.Debug >= 1 then
if epoe then
-- Why is this even present here?
epoe.MsgC(Color(0, 255, 0), "[WebAudio] ")
epoe.MsgC(Color(255, 255, 255), str)
epoe.Print("")
end
pac.Message(Color(0, 255, 0), "[WebAudio] ", Color(255, 255, 255), str, ...)
end
if webaudio.Debug >= 2 then
if easylua then
easylua.PrintOnServer("[WebAudio] " .. str .. ' ' .. table.concat({...}, ', '))
end
end
end
function webaudio.GetSampleRate ()
return webaudio.SampleRate
end
local volume = GetConVar("volume")
local snd_mute_losefocus = GetConVar("snd_mute_losefocus")
pac.AddHook("Think", "webaudio", function()
if not webaudio.Browser.IsInitialized () then
if not webaudio.Browser.IsInitializing () then
webaudio.Browser.Initialize ()
end
return
end
-- Update volume
if not system.HasFocus() and snd_mute_losefocus:GetBool() then
-- Garry's Mod not in foreground and we're not supposed to be making any sound
webaudio.Browser.SetVolume(0)
else
webaudio.Browser.SetVolume(volume:GetFloat())
end
webaudio.Streams.Think()
webaudio.Browser.Think()
end)