PLUGIN.Title = "Sacks Remover"
PLUGIN.Description = "Removes dropped lootable Sacks"
PLUGIN.Author = "Adam Mellor"
PLUGIN.Version = "1.3.2"
PLUGIN.ResourceID = 525
local dateTime = util.GetStaticPropertyGetter( System.DateTime, 'Now' )
function PLUGIN:Init()
print( self.Title .. " v" .. self.Version .. " Loading..." )
UseExitReason = new( cs.gettype( "UseExitReason, Assembly-CSharp" ) )
oxmin_plugin = plugins.Find("oxmin")
if ( not oxmin_plugin ) then
self.oxmin = false
else
self.oxmin = true
self.FLAG_canSacks = oxmin.AddFlag("canSacks")
print( self.Title .. ": canSacks oxmin flag successfully added!")
end
self:loadSettings()
if not self.Settings then return end --if Settings has not loaded then something is seriously wrong
self:loadStats()
self:AddChatCommand( "sacks", self.cmdSacks )
self.LastRunTime = false
if self.Settings.Timer > 0 then
self:startTimer()
end
print( self.Title .. " v" .. self.Version .. " Loaded." )
end
function PLUGIN:Unload( )
print( self.Title .. ": Unload, stopping timer." )
self:stopTimer()
end
function PLUGIN:loadSettings()
self.SettingsFile = util.GetDatafile( "Sacks Remover" )
local txt = self.SettingsFile:GetText()
if ( txt ~= "" ) then
self.Settings = json.decode( txt )
print( self.Title .. ": Settings file loaded. " )
else
print( self.Title .. ": Settings file not found. Creating new Settings file..." )
self.Settings = { ['Debug'] = false, ['ChatHandle'] = 'Sacks Remover', ['Timer'] = 300, ['LifeTime'] = 1800, ['Range'] = 0, ['saveStats'] = false, [ 'statsLevel' ] = 0 }
self:saveSettings()
end
end
function PLUGIN:saveSettings()
self.SettingsFile:SetText( json.encode( self.Settings, { indent = true } ) )
self.SettingsFile:Save()
end
function PLUGIN:loadStats()
self.StatsFile = util.GetDatafile( "Sacks Remover - Stats" )
local txt = self.StatsFile:GetText()
if ( txt ~= "" ) then
self.Stats = json.decode( txt )
print( self.Title .. ": Stats file loaded. " )
else
print( self.Title .. ": Stats file not found. Creating new Stats file..." )
self:clearStats()
end
end
function PLUGIN:clearStats()
self.Stats = nil
self.Stats = {}
self.Stats = { [ 'Total' ] = { [ 'Duration' ] = 0, [ 'Matches' ] = 0, [ 'Cleared' ] = 0, [ 'Objects' ] = 0, [ 'Loops' ] = 0 },
[ 'Average' ] = { [ 'Duration' ] = 0, [ 'Matches' ] = 0, [ 'Cleared' ] = 0, [ 'Objects' ] = 0 },
[ 'Duration' ] = { [ 'Top' ] = {}, [ 'Bottom' ] = {} },
[ 'Matches' ] = { [ 'Top' ] = {}, [ 'Bottom' ] = {} },
[ 'Cleared' ] = { [ 'Top' ] = {}, [ 'Bottom' ] = {} },
[ 'Objects' ] = { [ 'Top' ] = {}, [ 'Bottom' ] = {} }
}
self:saveStats()
end
function PLUGIN:saveStats()
self.StatsFile:SetText( json.encode( self.Stats, { indent = true } ) )
self.StatsFile:Save()
end
function PLUGIN:SendHelpText( netuser )
rust.SendChatToUser( netuser, self.Settings.ChatHandle, "Use /sacks to list all the Sacks Remover commands!" )
end
function PLUGIN:CanUserAdmin ( netuser )
if ( netuser:CanAdmin() ) or ( oxmin_Plugin:HasFlag(netuser, self.FLAG_canSacks, false) ) then
return true
else
return false
end
end
function PLUGIN:stopTimer()
if self.Timer then
self.Timer:Destroy()
self.Timer = nil
end
end
function PLUGIN:startTimer()
if self.Timer then
self:stopTimer()
end
if not self.Timer then
self.Timer = timer.Repeat( self.Settings.Timer , function() self:clearSacks( true, 0, nil ) end )
else
print( self.Title .. ": startTimer, Unable to stop the timer, something is wrong." )
end
end
function PLUGIN:printHelp( netuser )
rust.SendChatToUser( netuser, self.Settings.ChatHandle, "Use /sacks show [ range ] - to show the number of LootSacks on the server" )
rust.SendChatToUser( netuser, self.Settings.ChatHandle, "Use /sacks clear [ range ] - to empty and remove the LootSacks on the server" )
rust.SendChatToUser( netuser, self.Settings.ChatHandle, ".......optional value [ range ] - limits removing Sacks to within the specified range (number of meters)" )
rust.SendChatToUser( netuser, self.Settings.ChatHandle, "Use /sacks timer - toggles the autommatic timer on and off." )
end
function PLUGIN:cmdSacks( netuser, cmd, args )
if ( netuser and not self:CanUserAdmin( netuser ) ) then return false end
local range = 0
if args[ 2 ] and tonumber( args [ 2 ] ) then
range = tonumber( args [ 2 ] )
else
range = self.Settings.Range
end
local srcCoords = netuser.playerClient.lastKnownPosition
if #args == 0 then
self:printHelp( netuser )
return
elseif ( args[1] == 'help' ) then
self:printHelp( netuser )
return
elseif ( args[1] == 'printStats' ) then
self:printStats()
elseif ( args[1] == 'clearStats' ) then
self:clearStats()
rust.SendChatToUser( netuser, self.Settings.ChatHandle, 'Sacks Remover Stastics Cleared' )
elseif ( args[1] == 'debug' ) then
if self.Settings.Debug then
self.Settings.Debug = false
self:saveSettings()
print( self.Title .. ": Debug messages disabled." )
else
self.Settings.Debug = true
print( self.Title .. ": Debug messages enabled." )
end
elseif ( args[1] == 'show' ) then
local msg = self:clearSacks( false, range, srcCoords )
rust.SendChatToUser( netuser, self.Settings.ChatHandle, msg )
elseif ( args[1] == 'clear' ) then
local msg = self:clearSacks( true, range, srcCoords )
rust.SendChatToUser( netuser, self.Settings.ChatHandle, msg )
elseif ( args[1] == 'timer' ) then
if self.Timer then
self:stopTimer()
rust.SendChatToUser( netuser, self.Settings.ChatHandle, 'Timer Stopped' )
else
if self.Settings.Timer > 0 then
self:startTimer()
rust.SendChatToUser( netuser, self.Settings.ChatHandle, 'Timer Started' )
end
end
end
end
function PLUGIN:clearSacks( clear, range, srcCoords )
if clear then
self.empty = true
else
self.empty = false
end
if not srcCoords and range > 0 then
return false
end
local timeStart = dateTime()
if not self.LastRunTime then self.LastRunTime = timeStart end
local timeDelta = timeStart:Subtract( self.LastRunTime ).Ticks / 10000 / 1000 -- Ticks to Milliseconds to Seconds
if self.Settings.Debug then
print( "[ timeDelta ] = " .. tostring( timeDelta ), type( timeDelta ) )
end
local stats = {}
stats.Type = {}
objects = {}
objects.FindByClass = util.GetStaticMethod( UnityEngine.Resources._type, "FindObjectsOfTypeAll" )
stats.Objects = 0
stats.Matches = 0
stats.Cleared = 0
stats.Matches = 0
local inrange = 0
--local tbl = {}
local Objects = objects.FindByClass( Rust.LootableObject._type )
for i = 0, tonumber( Objects.Length - 1 ) do
local LootableObject = Objects[ i ];
stats.Objects = stats.Objects + 1
local objType = tostring( LootableObject.Name )
if self.Settings.statsLevel >= 2 then
if not stats.Type[ objType ] then stats.Type[ objType ] = 0 end
stats.Type[ objType ] = stats.Type[ objType ] + 1
end
local dist = 0
if objType == 'LootSack(Clone)' then
stats.Matches = stats.Matches + 1
if self.Settings.Debug then print( self.Title .. ": clearSacks ( " .. tostring( LootableObject ) .. " )." ) end
if range > 0 and srcCoords then
if self.Settings.Debug then print( self.Title .. ": clearSacks ( " .. tostring( LootableObject.collider.transform.position ) .. " )." ) end
dstCoords = LootableObject.collider.transform.position
if srcCoords and dstCoords then
dist = math.floor( math.sqrt( ( srcCoords.x - dstCoords.x) ^2 + ( srcCoords.z - dstCoords.z ) ^2 ) )
end
end
if range == 0 or ( dist > 0 and range >= dist ) then
inrange = inrange + 1
local timeToClear = false
if self.Settings.LifeTime == 0 then timeToClear = true end
if LootableObject.lifeTime > self.Settings.LifeTime then
LootableObject.lifeTime = self.Settings.LifeTime
else
if LootableObject.lifeTime - timeDelta > 0 then
LootableObject.lifeTime = LootableObject.lifeTime - timeDelta
else
timeToClear = true
--LootableObject.lifeTime = 1
end
end
if self.Settings.Debug then print( self.Title .. ": clearSacks, LootableObject.lifeTime = " ..tostring( LootableObject.lifeTime ) ) end -- time the bag hangs around after it has been opened once, in seconds
if self.empty and ( srcCoords or timeToClear ) then
if self.Settings.Debug then print( self.Title .. ": clearSacks, emptying LootSack." ) end
LootableObject._inventory:Clear()
if LootableObject._inventory.anyOccupiedSlots then
if self.Settings.Debug then print( self.Title .. ": clearSacks, Failed to empty LootSack." ) end
else
if self.Settings.Debug then print( self.Title .. ": clearSacks, LootSack emptied." ) end
LootableObject:OnUseExit( _, UseExitReason.Destroy )
if self.Settings.Debug then print( self.Title .. ": clearSacks ( " .. tostring( LootableObject ) .. " )." ) end
stats.Cleared = stats.Cleared + 1
end
end
end
end
end
local timeEnd = dateTime()
self.LastRunTime = timeStart
if self.Settings.saveStats then
self:calcStats( timeStart, timeEnd, stats )
end
local msg = "Found " .. tostring( inrange ) .. ' / ' .. tostring( stats.Matches ) .. " LootSacks in Range, Cleared " .. stats.Cleared .. "."
if self.Settings.Debug then print( self.Title .. ": clearSacks, " .. msg ) end
return msg
end
function PLUGIN:getTopFive( new, oldTable , Key )
local NewTable = oldTable
if #oldTable < 5 then
table.insert( NewTable, new )
else
for key, value in ipairs( oldTable ) do
if new[ Key ] > value[ Key ] then
table.insert( NewTable, key, new )
if #NewTable > 5 then
table.remove( NewTable )
end
break
end
end
end
return NewTable
end
function PLUGIN:getBottomFive( new, oldTable , Key )
local NewTable = oldTable
if #oldTable < 5 then
table.insert( NewTable, new )
else
for key, value in ipairs( oldTable ) do
if new[ Key ] < value[ Key ] then
table.insert( NewTable, key, new )
if #NewTable > 5 then
table.remove( NewTable )
end
break
end
end
end
return NewTable
end
function PLUGIN:printStats( )
print( "Total - Duration = " .. self.Stats.Total.Duration )
print( "Total - Matches = " .. self.Stats.Total.Matches )
print( "Total - Cleared = " .. self.Stats.Total.Cleared )
print( "Total - Objects = " .. self.Stats.Total.Objects )
print( "Average - Duration = " .. self.Stats.Average.Duration )
-- print( "Average - Matches = " .. self.Stats.Average.Matches )
print( "Average - Cleared = " .. self.Stats.Average.Cleared )
print( "Average - Objects = " .. self.Stats.Average.Objects )
print( "Top 5 - Duration:" )
for key, value in pairs( self.Stats.Duration.Top ) do print( "[ " .. tostring( key ) .. " ] = " .. value.Duration ) end
print( "Bottom 5 - Duration:" )
for key, value in pairs( self.Stats.Duration.Bottom ) do print( "[ " .. tostring( key ) .. " ] = " .. value.Duration ) end
print( "Top 5 - Matches:" )
for key, value in pairs( self.Stats.Matches.Top ) do print( "[ " .. tostring( key ) .. " ] = " .. value.Matches ) end
print( "Bottom 5 - Matches:" )
for key, value in pairs( self.Stats.Matches.Bottom ) do print( "[ " .. tostring( key ) .. " ] = " .. value.Matches ) end
print( "Top 5 - Cleared:" )
for key, value in pairs( self.Stats.Cleared.Top ) do print( "[ " .. tostring( key ) .. " ] = " .. value.Cleared ) end
print( "Bottom 5 - Cleared:" )
for key, value in pairs( self.Stats.Cleared.Bottom ) do print( "[ " .. tostring( key ) .. " ] = " .. value.Cleared ) end
print( "Top 5 - Objects:" )
for key, value in pairs( self.Stats.Objects.Top ) do print( "[ " .. tostring( key ) .. " ] = " .. value.Objects ) end
print( "Bottom 5 - Objects:" )
for key, value in pairs( self.Stats.Objects.Bottom ) do print( "[ " .. tostring( key ) .. " ] = " .. value.Objects ) end
end
function PLUGIN:calcStats( timeStart, timeEnd, newStats )
--newStats.Duration = timeEnd - timeStart
newStats.Duration = timeEnd:Subtract( timeStart ).Ticks / 10000 --10K Ticks in a millisecond
self.Stats.Total.Loops = self.Stats.Total.Loops + 1
if not self.Stats.Matches.Loops then self.Stats.Matches.Loops = 0 end
if not self.Stats.Cleared.Loops then self.Stats.Cleared.Loops = 0 end
if not self.Stats.Objects.Loops then self.Stats.Objects.Loops = 0 end
if newStats.Matches > 0 then
self.Stats.Matches.Loops = self.Stats.Matches.Loops + 1
end
if newStats.Cleared > 0 then
self.Stats.Cleared.Loops = self.Stats.Cleared.Loops + 1
end
if newStats.Objects > 114 then
self.Stats.Objects.Loops = self.Stats.Objects.Loops + 1
end
self.Stats.Total.Duration = self.Stats.Total.Duration + newStats.Duration
self.Stats.Total.Matches = self.Stats.Total.Matches + newStats.Matches
self.Stats.Total.Cleared = self.Stats.Total.Cleared + newStats.Cleared
self.Stats.Total.Objects = self.Stats.Total.Objects + newStats.Objects
self.Stats.Average.Duration = self.Stats.Total.Duration / self.Stats.Total.Loops
self.Stats.Average.Matches = self.Stats.Total.Matches / self.Stats.Matches.Loops
self.Stats.Average.Cleared = self.Stats.Total.Cleared / self.Stats.Cleared.Loops
self.Stats.Average.Objects = self.Stats.Total.Objects / self.Stats.Objects.Loops
if self.Settings.statsLevel >= 1 then
if newStats.Duration > self.Stats.Average.Duration then
self.Stats.Duration.Top = self:getTopFive( newStats, self.Stats.Duration.Top, 'Duration' )
else
self.Stats.Duration.Bottom = self:getBottomFive( newStats, self.Stats.Duration.Bottom, 'Duration' )
end
end
if self.Settings.statsLevel >= 2 then
if newStats.Matches > self.Stats.Average.Matches then
self.Stats.Matches.Top = self:getTopFive( newStats, self.Stats.Matches.Top, 'Matches' )
elseif newStats.Matches > 0 then
self.Stats.Matches.Bottom = self:getBottomFive( newStats, self.Stats.Matches.Bottom, 'Matches' )
end
end
if self.Settings.statsLevel >= 2 then
if newStats.Cleared > self.Stats.Average.Cleared then
self.Stats.Cleared.Top = self:getTopFive( newStats, self.Stats.Cleared.Top, 'Cleared' )
elseif newStats.Cleared > 0 then
self.Stats.Cleared.Bottom = self:getBottomFive( newStats, self.Stats.Cleared.Bottom, 'Cleared' )
end
end
if self.Settings.statsLevel >= 2 then
if newStats.Objects > self.Stats.Average.Objects then
self.Stats.Objects.Top = self:getTopFive( newStats, self.Stats.Objects.Top, 'Objects' )
elseif newStats.Objects > 112 then
self.Stats.Objects.Bottom = self:getBottomFive( newStats, self.Stats.Objects.Bottom, 'Objects' )
end
end
self:saveStats()
end