699 lines
17 KiB
Lua
699 lines
17 KiB
Lua
|
|
--
|
|
-- Copyright (C) 2017-2020 DBotThePony
|
|
|
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
-- of this software and associated documentation files (the "Software"), to deal
|
|
-- in the Software without restriction, including without limitation the rights
|
|
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
-- of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
-- subject to the following conditions:
|
|
|
|
-- The above copyright notice and this permission notice shall be included in all copies
|
|
-- or substantial portions of the Software.
|
|
|
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
-- DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
local PhysObj = FindMetaTable('PhysObj')
|
|
local vectorMeta = FindMetaTable('Vector')
|
|
local vehicleMeta = FindMetaTable('Vehicle')
|
|
local entMeta = FindMetaTable('Entity')
|
|
local Color = Color
|
|
local math = math
|
|
local ipairs = ipairs
|
|
local assert = assert
|
|
local select = select
|
|
local language = language
|
|
local list = list
|
|
local pairs = pairs
|
|
local CLIENT = CLIENT
|
|
|
|
_G.angle_empty = Angle()
|
|
_G.angle_up = Angle(90)
|
|
_G.angle_down = Angle(-90)
|
|
_G.angle_left = Angle(0, 90)
|
|
_G.angle_right = Angle(0, -90)
|
|
|
|
function PhysObj:SetAngleVelocity(newAngle)
|
|
return self:AddAngleVelocity(newAngle - self:GetAngleVelocity())
|
|
end
|
|
|
|
PhysObj.DLibSetMass = PhysObj.DLibSetMass or PhysObj.SetMass
|
|
PhysObj.DLibEnableCollisions = PhysObj.DLibEnableCollisions or PhysObj.EnableCollisions
|
|
PhysObj.DLibEnableDrag = PhysObj.DLibEnableDrag or PhysObj.EnableDrag
|
|
PhysObj.DLibEnableMotion = PhysObj.DLibEnableMotion or PhysObj.EnableMotion
|
|
PhysObj.DLibEnableGravity = PhysObj.DLibEnableGravity or PhysObj.EnableGravity
|
|
|
|
function PhysObj:SetMass(newMass)
|
|
if newMass <= 0 then
|
|
print(debug.traceback('Mass can not be lower or equal to 0!', 2))
|
|
return
|
|
end
|
|
|
|
return self:DLibSetMass(newMass)
|
|
end
|
|
|
|
local worldspawn, worldspawnPhys
|
|
|
|
-- shut up dumb addons
|
|
function PhysObj:EnableCollisions(newStatus)
|
|
worldspawn = worldspawn or Entity(0)
|
|
worldspawnPhys = worldspawnPhys or worldspawn:GetPhysicsObject()
|
|
|
|
if worldspawnPhys == self then
|
|
print(debug.traceback('Attempt to call :EnableCollisions() on World PhysObj!', 2))
|
|
return
|
|
end
|
|
|
|
return self:DLibEnableCollisions(newStatus)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname Vector:Copy
|
|
|
|
@desc
|
|
Same as doing `Vector(self)`
|
|
@enddesc
|
|
|
|
@returns
|
|
Vector
|
|
]]
|
|
function vectorMeta:Copy()
|
|
return Vector(self)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname Vector:__call
|
|
|
|
@returns
|
|
Vector: copy
|
|
]]
|
|
function vectorMeta:__call()
|
|
return Vector(self)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname Vector:ToNative
|
|
|
|
@returns
|
|
Vector: self
|
|
]]
|
|
function vectorMeta:ToNative()
|
|
return self
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname Vector:IsNormalized
|
|
|
|
@returns
|
|
boolean
|
|
]]
|
|
function vectorMeta:IsNormalized()
|
|
return self.x <= 1 and self.y <= 1 and self.z <= 1 and self.x >= -1 and self.y >= -1 and self.z >= -1
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname Vector:Receive
|
|
@args Vector from
|
|
|
|
@returns
|
|
Vector: self
|
|
]]
|
|
function vectorMeta:Receive(target)
|
|
local x, y, z = target.x, target.y, target.z
|
|
self.x, self.y, self.z = x, y, z
|
|
return self
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname Vector:RotateAroundAxis
|
|
@args Vector axis, number rotation
|
|
|
|
@returns
|
|
Vector: self
|
|
]]
|
|
function vectorMeta:RotateAroundAxis(axis, rotation)
|
|
local ang = self:Angle()
|
|
ang:RotateAroundAxis(axis, rotation)
|
|
return self:Receive(ang:Forward() * self:Length())
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname Vector:ToColor
|
|
|
|
@returns
|
|
Color
|
|
]]
|
|
function vectorMeta:ToColor()
|
|
return Color(self.x * 255, self.y * 255, self.z * 255)
|
|
end
|
|
|
|
local type = luatype
|
|
|
|
--[[
|
|
@doc
|
|
@fname Vector:WithinAABox
|
|
@args Vector mins, Vector maxs
|
|
|
|
@desc
|
|
can also accept `LVector`
|
|
@enddesc
|
|
|
|
@returns
|
|
boolean
|
|
]]
|
|
function vectorMeta:WithinAABox(mins, maxs)
|
|
if type(mins) ~= 'Vector' and type(mins) ~= 'LVector' then
|
|
error('Vector:WithinAABox(' .. type(mins) .. ', ' .. type(maxs) .. ') - invalid call')
|
|
end
|
|
|
|
if type(maxs) ~= 'Vector' and type(maxs) ~= 'LVector' then
|
|
error('Vector:WithinAABox(' .. type(mins) .. ', ' .. type(maxs) .. ') - invalid call')
|
|
end
|
|
|
|
return self.x >= mins.x
|
|
and self.y >= mins.y
|
|
and self.z >= mins.z
|
|
and self.x <= maxs.x
|
|
and self.y <= maxs.y
|
|
and self.z <= maxs.z
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname sql.EQuery
|
|
@args string query
|
|
|
|
@desc
|
|
Same as gmod's sql.Query except it prints errors in console when one occures
|
|
@enddesc
|
|
|
|
@returns
|
|
any: returned value from database
|
|
]]
|
|
function sql.EQuery(...)
|
|
local data = sql.Query(...)
|
|
|
|
if data == false then
|
|
DLib.Message('SQL: ', ...)
|
|
DLib.Message(sql.LastError())
|
|
end
|
|
|
|
return data
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.progression
|
|
@args number self, number min, number max, number middle = nil
|
|
|
|
@returns
|
|
number: position of self between min and max in 0-1 range, or 0-1-0 is middle is not nil
|
|
]]
|
|
function math.progression(self, min, max, middle)
|
|
if self < min then return 0 end
|
|
|
|
if middle then
|
|
if self < min or self >= max then return 0 end
|
|
|
|
if self < middle then
|
|
return math.min((self - min) / (middle - min), 1)
|
|
elseif self > middle then
|
|
return 1 - math.min((self - middle) / (max - middle), 1)
|
|
elseif self == middle then
|
|
return 1
|
|
end
|
|
end
|
|
|
|
return math.min((self - min) / (max - min), 1)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.equal
|
|
@args vararg numbers
|
|
|
|
@returns
|
|
boolean
|
|
]]
|
|
function math.equal(...)
|
|
local amount = select('#', ...)
|
|
assert(amount > 1, 'At least two numbers are required!')
|
|
local lastValue
|
|
|
|
for i = 1, amount do
|
|
local value = select(i, ...)
|
|
lastValue = lastValue or value
|
|
if value ~= lastValue then return false end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.average
|
|
@args vararg numbers
|
|
|
|
@returns
|
|
number: the average
|
|
]]
|
|
function math.average(...)
|
|
local amount = select('#', ...)
|
|
assert(amount > 1, 'At least two numbers are required!')
|
|
local total = 0
|
|
|
|
for i = 1, amount do
|
|
total = total + select(i, ...)
|
|
end
|
|
|
|
return total / amount
|
|
end
|
|
|
|
local type = type
|
|
local table = table
|
|
local unpack = unpack
|
|
local buffer = {}
|
|
|
|
local function tbezier(t, values, amount)
|
|
assert(type(t) == 'number', 'invalid T variable')
|
|
assert(t >= 0 and t <= 1, '0 <= t <= 1!')
|
|
assert(#values >= 2, 'at least two values must be provided')
|
|
local a, b = values[1], values[2]
|
|
|
|
-- linear
|
|
if amount == 2 then
|
|
return a + (b - a) * t
|
|
-- square
|
|
elseif amount == 3 then
|
|
return (1 - t):pow(2) * a + 2 * t * (1 - t) * b + t:pow(2) * values[3]
|
|
-- cube
|
|
elseif amount == 4 then
|
|
return (1 - t):pow(3) * a + 3 * t * (1 - t):pow(2) * b + 3 * t:pow(2) * (1 - t) * values[3] + t:pow(3) * values[4]
|
|
end
|
|
|
|
for point = 1, amount do
|
|
local point1 = values[point]
|
|
local point2 = values[point + 1]
|
|
if not point2 then break end
|
|
buffer[point] = point1 + (point2 - point1) * t
|
|
end
|
|
|
|
return tbezier(t, buffer, amount - 1)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.bezier
|
|
@args number t, vararg numbers
|
|
|
|
@returns
|
|
number
|
|
]]
|
|
function math.bezier(t, ...)
|
|
return tbezier(t, {...}, select('#', ...))
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.tbezier
|
|
@args number t, table numbers
|
|
|
|
@returns
|
|
number
|
|
]]
|
|
-- accepts table
|
|
function math.tbezier(t, values)
|
|
return tbezier(t, values, #values)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.tformat
|
|
@args number time
|
|
|
|
@returns
|
|
table: formatted table
|
|
]]
|
|
function math.tformat(time)
|
|
local centuries, years, months, weeks, days, hours, minutes, seconds = math.tformatVararg(time)
|
|
return {
|
|
centuries = centuries, years = years, months = months, weeks = weeks, days = days, hours = hours, minutes = minutes, seconds = seconds
|
|
}
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.tformatVararg
|
|
@args number time
|
|
|
|
@returns
|
|
number: centuries
|
|
number: years
|
|
number: months
|
|
number: weeks
|
|
number: days
|
|
number: hours
|
|
number: minutes
|
|
number: seconds
|
|
]]
|
|
function math.tformatVararg(time)
|
|
assert(type(time) == 'number', 'Invalid time provided.')
|
|
|
|
if time > 0xFFFFFFFFFF then
|
|
error('Value is too big! Maximum is ' .. 0xFFFFFFFFFF)
|
|
elseif time <= 1 then
|
|
return 0, 0, 0, 0, 0, 0, 0, 0
|
|
end
|
|
|
|
local centuries = (time - time % 0xBBF81E00) / 0xBBF81E00
|
|
time = time - centuries * 0xBBF81E00
|
|
|
|
local years = (time - time % 0x01E13380) / 0x01E13380
|
|
time = time - years * 0x01E13380
|
|
|
|
local months = ((time - time % 0x00278D00) / 0x00278D00):min(11)
|
|
time = time - months * 0x00278D00
|
|
|
|
local weeks = (time - time % 604800) / 604800
|
|
time = time - weeks * 604800
|
|
|
|
local days = (time - time % 86400) / 86400
|
|
time = time - days * 86400
|
|
|
|
local hours = (time - time % 3600) / 3600
|
|
time = time - hours * 3600
|
|
|
|
local minutes = (time - time % 60) / 60
|
|
time = time - minutes * 60
|
|
|
|
local seconds = time:floor()
|
|
|
|
return centuries, years, months, weeks, days, hours, minutes, seconds
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.untformat
|
|
@args table time
|
|
|
|
@desc
|
|
reverse of table format of number
|
|
@enddesc
|
|
|
|
@returns
|
|
number
|
|
]]
|
|
function math.untformat(time)
|
|
assert(type(time) == 'table', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
|
|
return math.untformatVararg(time.centuries, time.years, time.months, time.weeks, time.days, time.hours, time.minutes, time.seconds)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname math.untformatVararg
|
|
@args number centuries, number years, number months, number weeks, number days, number hours, number minutes, number seconds
|
|
|
|
@desc
|
|
reverse of time numbers format of number
|
|
@enddesc
|
|
|
|
@returns
|
|
number
|
|
]]
|
|
function math.untformatVararg(centuries, years, months, weeks, days, hours, minutes, seconds)
|
|
assert(type(centuries) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
assert(type(years) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
assert(type(months) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
assert(type(weeks) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
assert(type(days) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
assert(type(hours) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
assert(type(minutes) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
assert(type(seconds) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.')
|
|
|
|
return centuries * 0xBBF81E00
|
|
+ years * 0x01E13380
|
|
+ months * 0x00278D00
|
|
+ weeks * 604800
|
|
+ days * 86400
|
|
+ hours * 3600
|
|
+ minutes * 60
|
|
+ seconds
|
|
end
|
|
|
|
do
|
|
local daysIn = {
|
|
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
|
}
|
|
|
|
local _timeCached = {}
|
|
local _timeCachedHigh = {}
|
|
|
|
for month = 1, 12 do
|
|
local stamp1 = 0
|
|
local stamp2 = 0
|
|
|
|
for _month = 1, month - 1 do
|
|
if _month == 2 then
|
|
stamp1 = stamp1 + 29 * 86400
|
|
stamp2 = stamp2 + 28 * 86400
|
|
else
|
|
stamp1 = stamp1 + daysIn[_month] * 86400
|
|
stamp2 = stamp2 + daysIn[_month] * 86400
|
|
end
|
|
end
|
|
|
|
_timeCachedHigh[month] = stamp1
|
|
_timeCached[month] = stamp2
|
|
end
|
|
|
|
local yearLength = 31536000
|
|
local longYearLength = 31622400
|
|
local isnumber = isnumber
|
|
|
|
function math.dateToTimestamp(year, month, day, hour, minute, second)
|
|
assert(isnumber(year), 'year is not a number or missing')
|
|
assert(isnumber(month), 'month is not a number or missing')
|
|
assert(isnumber(day), 'day is not a number or missing')
|
|
assert(isnumber(hour), 'hour is not a number or missing')
|
|
assert(isnumber(minute), 'minute is not a number or missing')
|
|
assert(isnumber(second), 'second is not a number or missing')
|
|
|
|
assert(year >= 1970, 'year < 1970 is not supported yet')
|
|
assert(month >= 1 and month < 13, 'invalid month')
|
|
assert(day >= 1 and day <= 31, 'invalid day')
|
|
assert(hour >= 0 and hour <= 24, 'invalid hour')
|
|
assert(minute >= 0 and minute <= 60, 'invalid minute')
|
|
assert(second >= 0 and second <= 60, 'invalid second')
|
|
|
|
local yearS, peaks, skips, shift = year - 1970, 0, 0, 0
|
|
|
|
if year > 2000 then
|
|
-- ???
|
|
peaks = ((year - 1600) / 400):floor()
|
|
yearS = yearS - peaks
|
|
shift = -86400 * peaks
|
|
|
|
skips = (((year - 1900) / 100):floor() - peaks):max(0)
|
|
yearS = yearS - skips
|
|
|
|
local peak2 = (((year - 1968) / 4):floor() - skips):max(0)
|
|
yearS = yearS - peak2
|
|
peaks = peaks + peak2
|
|
elseif year < 2000 then
|
|
peaks = ((year - 1968) / 4):floor()
|
|
yearS = yearS - peaks
|
|
else
|
|
peaks = 7
|
|
yearS = yearS - 7
|
|
end
|
|
|
|
return peaks * longYearLength + skips * yearLength + yearS * yearLength +
|
|
((year % 400 == 0 or year % 100 ~= 0 and year % 4 == 0) and _timeCachedHigh[month] or _timeCached[month]) +
|
|
(day - 1) * 86400 + hour * 3600 + minute * 60 + second + shift
|
|
end
|
|
end
|
|
|
|
local CLIENT = CLIENT
|
|
local hook = hook
|
|
local net = net
|
|
|
|
if SERVER then
|
|
net.pool('dlib.limithitfix')
|
|
end
|
|
|
|
local plyMeta = FindMetaTable('Player')
|
|
|
|
--[[
|
|
@doc
|
|
@fname Player:LimitHit
|
|
|
|
@desc
|
|
This function no longer produce limit hit message when called clientside
|
|
this function is internal and is used by gmod itself
|
|
but the override allows you to put !g:Player:CheckLimit in shared code of toolguns
|
|
@enddesc
|
|
]]
|
|
function plyMeta:LimitHit(limit)
|
|
-- we call CheckLimit() on client just for prediction
|
|
-- so when we actually hit limit - it can produce two messages because client will also try to
|
|
-- display this message by calling hook LimitHit. So, let's call that only once.
|
|
|
|
-- if you want to call this function clientside despite this text and warning
|
|
-- you can run hooks on LimitHit manually by doing so:
|
|
-- hook.Run('LimitHit', 'mylimit')
|
|
-- you shouldn't really call this function directly clientside
|
|
if CLIENT then return end
|
|
|
|
net.Start('dlib.limithitfix')
|
|
net.WriteString(limit)
|
|
net.Send(self)
|
|
end
|
|
|
|
if CLIENT then
|
|
net.receive('dlib.limithitfix', function()
|
|
hook.Run('LimitHit', net.ReadString())
|
|
end)
|
|
end
|
|
|
|
if CLIENT then
|
|
local surface = surface
|
|
surface._DLibPlaySound = surface._DLibPlaySound or surface.PlaySound
|
|
|
|
function surface.PlaySound(path, ...)
|
|
assert(type(path) == 'string', 'surface.PlaySound - string expected, got ' .. type(path))
|
|
local can = hook.Run('SurfaceEmitSound', path, ...)
|
|
if can == false then return end
|
|
return surface._DLibPlaySound(path, ...)
|
|
end
|
|
|
|
-- cache and speedup lookups a bit
|
|
local use_type = CreateConVar('dlib_screenscale', '1', {FCVAR_ARCHIVE}, 'Use screen height as screen scale parameter instead of screen width')
|
|
local dlib_screenscale_mul = CreateConVar('dlib_screenscale_mul', '1', {FCVAR_ARCHIVE}, 'GUI Scale multiplier')
|
|
DLib.dlib_screenscale_mul = dlib_screenscale_mul
|
|
local dlib_screenscale_mul_get = dlib_screenscale_mul:GetFloat(1):max(0)
|
|
local ScrWL = ScrWL
|
|
local ScrHL = ScrHL
|
|
local screenfunc
|
|
|
|
if use_type:GetBool() then
|
|
function screenfunc(modify)
|
|
return ScrHL() / 480 * modify * dlib_screenscale_mul_get
|
|
end
|
|
else
|
|
function screenfunc(modify)
|
|
return ScrWL() / 640 * modify * dlib_screenscale_mul_get
|
|
end
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname ScreenSize
|
|
@args number modify
|
|
|
|
@desc
|
|
same as ScreenScale but use screen height (by default)
|
|
behvaior can be changed by user
|
|
@enddesc
|
|
|
|
@returns
|
|
number
|
|
]]
|
|
function _G.ScreenSize(modify)
|
|
return screenfunc(modify)
|
|
end
|
|
|
|
local function dlib_screenscale_chages()
|
|
if use_type:GetBool() then
|
|
function screenfunc(modify)
|
|
return ScrHL() / 480 * modify
|
|
end
|
|
else
|
|
function screenfunc(modify)
|
|
return ScrWL() / 640 * modify
|
|
end
|
|
end
|
|
|
|
DLib.TriggerScreenSizeUpdate(ScrWL(), ScrHL(), ScrWL(), ScrHL())
|
|
end
|
|
|
|
cvars.AddChangeCallback('dlib_screenscale', dlib_screenscale_chages, 'DLib')
|
|
cvars.AddChangeCallback('dlib_screenscale_mul', function()
|
|
dlib_screenscale_mul_get = dlib_screenscale_mul:GetFloat(1):max(0)
|
|
DLib.TriggerScreenSizeUpdate(ScrWL(), ScrHL(), ScrWL(), ScrHL())
|
|
end, 'DLib')
|
|
end
|
|
|
|
local threads1T = {}
|
|
local threads1C = {}
|
|
local threads2T = {}
|
|
local threads2C = {}
|
|
local SysTime = SysTime
|
|
local coroutine = coroutine
|
|
local maximal = 0
|
|
|
|
--[[
|
|
@doc
|
|
@fname coroutine.syswait
|
|
@args number seconds
|
|
|
|
@desc
|
|
like !g:coroutine.wait but use `SysTime()`
|
|
@enddesc
|
|
]]
|
|
function coroutine.syswait(seconds)
|
|
if type(seconds) ~= 'number'then
|
|
error('Invalid seconds amount provided')
|
|
end
|
|
|
|
local thread = assert(coroutine.running(), 'Not inside coroutine!')
|
|
|
|
if seconds < 0 then return end
|
|
|
|
table.insert(threads1C, thread)
|
|
table.insert(threads1T, SysTime() + seconds)
|
|
coroutine.yield()
|
|
end
|
|
|
|
hook.Add(SERVER and 'Tick' or 'Think', 'DLib.coroutine.syswait', function()
|
|
local stime = SysTime()
|
|
|
|
for i = 1, #threads2C do
|
|
local thread = threads2C[i]
|
|
local time = threads2T[i]
|
|
|
|
if time and time <= stime then
|
|
threads2C[i] = nil
|
|
threads2T[i] = nil
|
|
|
|
if coroutine.status(thread) == 'suspended' then
|
|
local status, err = coroutine.resume(thread)
|
|
|
|
if not status then
|
|
DLib.MessageError('Error executing thread:')
|
|
ErrorNoHalt(err .. '\n')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local sw1t, sw2t, sw1c, sw2c = threads1T, threads2T, threads1C, threads2C
|
|
threads2T = sw1t
|
|
threads1T = sw2t
|
|
threads2C = sw1c
|
|
threads1C = sw2c
|
|
end, 3)
|