mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 21:53:46 +03:00
Upload
This commit is contained in:
674
lua/pac3/libraries/animations.lua
Normal file
674
lua/pac3/libraries/animations.lua
Normal 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
|
||||
80
lua/pac3/libraries/expression.lua
Normal file
80
lua/pac3/libraries/expression.lua
Normal 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
|
||||
228
lua/pac3/libraries/haloex.lua
Normal file
228
lua/pac3/libraries/haloex.lua
Normal 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
|
||||
359
lua/pac3/libraries/luadata.lua
Normal file
359
lua/pac3/libraries/luadata.lua
Normal 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
|
||||
439
lua/pac3/libraries/resource.lua
Normal file
439
lua/pac3/libraries/resource.lua
Normal 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
|
||||
1645
lua/pac3/libraries/shader_params.lua
Normal file
1645
lua/pac3/libraries/shader_params.lua
Normal file
File diff suppressed because it is too large
Load Diff
670
lua/pac3/libraries/string_stream.lua
Normal file
670
lua/pac3/libraries/string_stream.lua
Normal 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
|
||||
123
lua/pac3/libraries/urlobj/cache.lua
Normal file
123
lua/pac3/libraries/urlobj/cache.lua
Normal 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
|
||||
505
lua/pac3/libraries/urlobj/crypto.lua
Normal file
505
lua/pac3/libraries/urlobj/crypto.lua
Normal 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
|
||||
262
lua/pac3/libraries/urlobj/queueitem.lua
Normal file
262
lua/pac3/libraries/urlobj/queueitem.lua
Normal 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
|
||||
703
lua/pac3/libraries/urlobj/urlobj.lua
Normal file
703
lua/pac3/libraries/urlobj/urlobj.lua
Normal 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
|
||||
280
lua/pac3/libraries/urltex.lua
Normal file
280
lua/pac3/libraries/urltex.lua
Normal 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
|
||||
1069
lua/pac3/libraries/webaudio.lua
Normal file
1069
lua/pac3/libraries/webaudio.lua
Normal file
File diff suppressed because it is too large
Load Diff
436
lua/pac3/libraries/webaudio/browser.lua
Normal file
436
lua/pac3/libraries/webaudio/browser.lua
Normal 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>
|
||||
|
||||
]==]
|
||||
473
lua/pac3/libraries/webaudio/stream.lua
Normal file
473
lua/pac3/libraries/webaudio/stream.lua
Normal 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
|
||||
61
lua/pac3/libraries/webaudio/streams.lua
Normal file
61
lua/pac3/libraries/webaudio/streams.lua
Normal 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
|
||||
68
lua/pac3/libraries/webaudio/urlogg.lua
Normal file
68
lua/pac3/libraries/webaudio/urlogg.lua
Normal 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)
|
||||
Reference in New Issue
Block a user