--
-- toptimes_server.lua
--
local deleteTimeOnline = true
local renameTimeOnline = true
SToptimesManager = {}
SToptimesManager.__index = SToptimesManager
SToptimesManager.instances = {}
---------------------------------------------------------------------------
-- Server
-- Handle events from Race
--
-- This is the 'interface' from Race
--
---------------------------------------------------------------------------
addEvent('onMapStarting')
addEventHandler('onMapStarting', g_Root,
function(mapInfo, mapOptions, gameOptions)
if g_SToptimesManager then
g_SToptimesManager:setModeAndMap( mapInfo.modename, mapInfo.name, gameOptions.statsKey )
end
end
)
addEvent('onPlayerPickUpRacePickup')
addEventHandler('onPlayerPickUpRacePickup', g_Root,
function(number, sort, model)
if sort == "vehiclechange" then
if model == 425 then
--outputChatBox ( "* " .. getPlayerName(source) .. " has got the Hunter!", getRootElement(), 255, 0, 0, true )
if g_SToptimesManager then
g_SToptimesManager:playerFinished( source, exports.race:getTimePassed())
end
end
end
end
)
addEvent('onPlayerFinish')
addEventHandler('onPlayerFinish', g_Root,
function(rank, time)
if g_SToptimesManager then
g_SToptimesManager:playerFinished( source, time)
end
end
)
addEventHandler('onResourceStop', g_ResRoot,
function()
if g_SToptimesManager then
g_SToptimesManager:unloadingMap()
end
end
)
addEventHandler('onPlayerQuit', g_Root,
function()
if g_SToptimesManager then
g_SToptimesManager:removePlayerFromUpdateList(source)
g_SToptimesManager:unqueueUpdate(source)
end
end
)
addEventHandler('onResourceStart', g_ResRoot,
function()
local raceInfo = getRaceInfo()
if raceInfo and g_SToptimesManager then
g_SToptimesManager:setModeAndMap( raceInfo.mapInfo.modename, raceInfo.mapInfo.name, raceInfo.gameOptions.statsKey )
end
end
)
function getRaceInfo()
local raceResRoot = getResourceRootElement( getResourceFromName( "race" ) )
return raceResRoot and getElementData( raceResRoot, "info" )
end
---------------------------------------------------------------------------
--
-- Events fired from here
--
---------------------------------------------------------------------------
addEvent("onPlayerToptimeImprovement")
---------------------------------------------------------------------------
---------------------------------------------------------------------------
--
-- SToptimesManager:create()
--
-- Create a SToptimesManager instance
--
---------------------------------------------------------------------------
function SToptimesManager:create()
local id = #SToptimesManager.instances + 1
SToptimesManager.instances[id] = setmetatable(
{
id = id,
playersWhoWantUpdates = {},
updateQueue = {},
serviceQueueTimer = nil,
displayTopCount = 12, -- Top number of times to display
mapTimes = nil, -- SMaptimes:create()
serverRevision = 0, -- To prevent redundant updating to clients
},
self
)
SToptimesManager.instances[id]:postCreate()
return SToptimesManager.instances[id]
end
---------------------------------------------------------------------------
--
-- SToptimesManager:destroy()
--
-- Destroy a SToptimesManager instance
--
---------------------------------------------------------------------------
function SToptimesManager:destroy()
SToptimesManager.instances[self.id] = nil
self.id = 0
end
---------------------------------------------------------------------------
--
-- SToptimesManager:postCreate()
--
--
--
---------------------------------------------------------------------------
function SToptimesManager:postCreate()
cacheSettings()
self.displayTopCount = g_Settings.numtimes
end
---------------------------------------------------------------------------
--
-- SToptimesManager:setModeAndMap()
--
-- Called when a new map has been loaded
--
---------------------------------------------------------------------------
function SToptimesManager:setModeAndMap( raceModeName, mapName, statsKey )
outputDebug( 'TOPTIMES', 'SToptimesManager:setModeAndMap ' .. raceModeName .. '<>' .. mapName )
-- Reset updatings from the previous map
self.playersWhoWantUpdates = {}
self.updateQueue = {}
if self.serviceQueueTimer then
killTimer(self.serviceQueueTimer)
end
self.serviceQueueTimer = nil
-- Remove old map times
if self.mapTimes then
self.mapTimes:flush() -- Ensure last stuff is saved
self.mapTimes:destroy()
end
-- Get map times for this map
self.mapTimes = SMaptimes:create( raceModeName, mapName, statsKey )
self.mapTimes:load()
-- Get the toptimes data ready to send
self:updateTopText()
end
---------------------------------------------------------------------------
--
-- SToptimesManager:unloadingMap()
--
-- Called when unloading
--
---------------------------------------------------------------------------
function SToptimesManager:unloadingMap()
if self.mapTimes then
self.mapTimes:flush() -- Ensure last stuff is saved
end
end
---------------------------------------------------------------------------
--
-- SToptimesManager:playerFinished()
--
-- If time is good enough, insert into database
--
---------------------------------------------------------------------------
function SToptimesManager:playerFinished( player, newTime, dateRecorded )
-- Check if top time recording is disabled for this player
if getElementData ( player, "toptimes" ) == "off" then
return
end
if not self.mapTimes then
outputDebug( 'TOPTIMES', 'SToptimesManager:playerFinished - self.mapTimes == nil' )
return
end
dateRecorded = dateRecorded or getRealDateTimeNowString()
local oldTime = self.mapTimes:getTimeForPlayer( player ) -- Can be false if no previous time
local newPos = self.mapTimes:getPositionForTime( newTime, dateRecorded )
-- See if time is an improvement for this player
if not oldTime or newTime < oldTime then
local oldPos = self.mapTimes:getIndexForPlayer( player )
triggerEvent("onPlayerToptimeImprovement", player, newPos, newTime, oldPos, oldTime, self.displayTopCount, self.mapTimes:getValidEntryCount() )
-- See if its in the top display
if newPos <= self.displayTopCount then
outputDebug( 'TOPTIMES', getPlayerName(player) .. ' got toptime position ' .. newPos )
end
outputChatBox("#ff6464[TT] #ffffff" ..getPlayerName(player) .. ' #ffffffmade a new toptime! Position: #ff6464' .. newPos .. ' #ffffffTime: #ff6464 ' .. convertMS(newTime), getRootElement(), 0, 171, 255, true)
if oldTime then
outputDebug( 'TOPTIMES', getPlayerName(player) .. ' new personal best ' .. newTime .. ' ' .. oldTime - newTime )
end
self.mapTimes:setTimeForPlayer( player, newTime, dateRecorded )
-- updateTopText if database was changed
if newPos <= self.displayTopCount then
self:updateTopText()
end
end
outputDebug( 'TOPTIMES', '++ SToptimesManager:playerFinished ' .. tostring(getPlayerName(player)) .. ' time:' .. tostring(newTime) )
end
function convertMS( timeMs )
local minutes = math.floor( timeMs / 60000 )
local timeMs = timeMs - minutes * 60000;
local seconds = math.floor( timeMs / 1000 )
local ms = timeMs - seconds * 1000;
return string.format( '%02d:%02d:%03d', minutes, seconds, ms );
end
---------------------------------------------------------------------------
--
-- SToptimesManager:updateTopText()
--
-- Update the toptimes client data for the current map
--
---------------------------------------------------------------------------
function SToptimesManager:updateTopText()
if not self.mapTimes then return end
-- Update data
-- Read top rows from map toptimes table and send to all players who want to know
self.toptimesDataForMap = self.mapTimes:getToptimes( self.displayTopCount )
self.serverRevision = self.serverRevision + 1
-- Queue send to all players
for i,player in ipairs(self.playersWhoWantUpdates) do
self:queueUpdate(player)
end
end
---------------------------------------------------------------------------
--
-- SToptimesManager:onServiceQueueTimer()
--
-- Pop a player off the updateQueue and send them an update
--
---------------------------------------------------------------------------
function SToptimesManager:onServiceQueueTimer()
outputDebug( 'TOPTIMES', 'SToptimesManager:onServiceQueueTimer()' )
-- Process next player
if #self.updateQueue > 0 and self.mapTimes then
local player = self.updateQueue[1]
local playerPosition = self.mapTimes:getIndexForPlayer( player )
clientCall( player, 'onServerSentToptimes', self.toptimesDataForMap, self.serverRevision, playerPosition );
end
table.remove(self.updateQueue,1)
-- Stop timer if end of update queue
if #self.updateQueue < 1 then
killTimer(self.serviceQueueTimer)
self.serviceQueueTimer = nil
end
end
---------------------------------------------------------------------------
--
-- SToptimesManager:addPlayerToUpdateList()
--
--
--
---------------------------------------------------------------------------
function SToptimesManager:addPlayerToUpdateList( player )
if not table.find( self.playersWhoWantUpdates, player) then
table.insert( self.playersWhoWantUpdates, player )
outputDebug( 'TOPTIMES', 'playersWhoWantUpdates : ' .. #self.playersWhoWantUpdates )
end
end
function SToptimesManager:removePlayerFromUpdateList( player )
table.removevalue( self.playersWhoWantUpdates, player )
end
---------------------------------------------------------------------------
--
-- SToptimesManager:queueUpdate()
--
--
--
---------------------------------------------------------------------------
function SToptimesManager:queueUpdate( player )
if not table.find( self.updateQueue, player) then
table.insert( self.updateQueue, player )
end
if not self.serviceQueueTimer then
self.serviceQueueTimer = setTimer( function() self:onServiceQueueTimer() end, 100, 0 )
end
end
function SToptimesManager:unqueueUpdate( player )
table.removevalue( self.updateQueue, player )
end
---------------------------------------------------------------------------
--
-- SToptimesManager:doOnClientRequestToptimesUpdates()
--
--
--
---------------------------------------------------------------------------
function SToptimesManager:doOnClientRequestToptimesUpdates( player, bOn, clientRevision )
outputDebug( 'TOPTIMES', 'SToptimesManager:onClientRequestToptimesUpdates: '
.. tostring(getPlayerName(player)) .. '<>' .. tostring(bOn) .. '< crev:'
.. tostring(clientRevision) .. '< srev:' .. tostring(self.serverRevision) )
if bOn then
self:addPlayerToUpdateList(player)
if clientRevision ~= self.serverRevision then
outputDebug( 'TOPTIMES', 'queueUpdate for'..getPlayerName(player) )
self:queueUpdate(player)
end
else
self:removePlayerFromUpdateList(player)
self:unqueueUpdate(player)
end
end
addEvent('onClientRequestToptimesUpdates', true)
addEventHandler('onClientRequestToptimesUpdates', getRootElement(),
function( bOn, clientRevision )
g_SToptimesManager:doOnClientRequestToptimesUpdates( source, bOn, clientRevision )
end
)
---------------------------------------------------------------------------
--
-- Commands and binds
--
--
--
---------------------------------------------------------------------------
addCommandHandler( "deletett",
function( player, cmd, place )
if deleteTimeOnline == false then return outputChatBox("#33ccffDeleting toptimes disabled by default",player,255,255,255,true) end
if not _TESTING and not isPlayerInACLGroup(player, g_Settings.admingroup) then
return
end
if g_SToptimesManager and g_SToptimesManager.mapTimes then
local row = g_SToptimesManager.mapTimes:deletetime(place)
if row then
g_SToptimesManager:updateTopText()
local mapName = tostring(g_SToptimesManager.mapTimes.mapName)
local placeText = place and " #" .. tostring(place) or ""
outputChatBox( "#FF6464[TT] #FFFFFF"..placeText.." " .. tostring(row.playerName) .. "#FFFFFF deleted by#ffffff " .. getPlayerName(player),getRootElement(), 255,55,0,true)
outputServerLog( "INFO: Top time"..placeText.." from '" ..tostring(row.playerName).. "' (" ..tostring(row.timeText).. " in " ..mapName.. ") deleted by " .. getAdminNameForLog(player) )
end
end
end
)
--[[addCommandHandler( "renamett",
function( player, cmd, place , newName)
if renameTimeOnline == false then return outputChatBox("#33ccffRenaming toptimes disabled by default",player,255,255,255,true) end
if not _TESTING and not isPlayerInACLGroup(player, g_Settings.admingroup) then
return
end
if g_SToptimesManager and g_SToptimesManager.mapTimes then
if tonumber(place) then
local oldname = g_SToptimesManager.mapTimes:renameTimeForPlayer(tonumber(place),newName)
if oldname then
g_SToptimesManager:updateTopText()
local mapName = tostring(g_SToptimesManager.mapTimes.mapName)
local placeText = place and " #" .. tostring(place) or ""
outputChatBox( "#FFFFFF[TT] #00AAFF"..placeText.." #FFFFFF" .. tostring(oldname) .. "#00AAFF renamed to #FFFFFF" .. tostring(newName),getRootElement(), 255,55,0,true)
outputServerLog( "INFO: Top time"..placeText.." from '" ..tostring(oldname).. "' (" ..tostring(row.timeText).. " in " ..mapName.. ") renamed by " .. getAdminNameForLog(player).." to '" .. tostring(newName).."'" )
end
end
end
end
)--]]
addCommandHandler( "renamett",
function( player, cmd, place , newName)
if renameTimeOnline == false then return outputChatBox("#33ccffRenaming toptimes disabled by default",player,255,255,255,true) end
if not _TESTING and not isPlayerInACLGroup(player, g_Settings.admingroup) then
accountname = getAccountName (getPlayerAccount(player))
if isObjectInACLGroup ( "user." .. accountname, aclGetGroup ( "Admin" ) ) then
return
end
end
if g_SToptimesManager and g_SToptimesManager.mapTimes then
if tonumber(place) then
local oldname = g_SToptimesManager.mapTimes:renameTimeForPlayer(tonumber(place),newName)
if oldname then
g_SToptimesManager:updateTopText()
local mapName = tostring(g_SToptimesManager.mapTimes.mapName)
--accountname = getAccountName (getPlayerAccount(player))
--if isObjectInACLGroup ( "user." .. accountname, aclGetGroup ( "Admin" ) ) then
local placeText = place and " #" .. tostring(place) or ""
outputChatBox( "#FF6464[TT] #FFFFFF"..placeText.." #FFFFFF" .. tostring(oldname) .. "#FFFFFF renamed to #FFFFFF" .. tostring(newName) .. " #FFFFFFby " .. getPlayerName(player),getRootElement(), 255,55,0,true)
else
outputChatBox("TEST!",thePlayer,255,125,0,true)
outputServerLog( "INFO: Top time"..placeText.." from '" ..tostring(oldname).. "' (" ..tostring(row.timeText).. " in " ..mapName.. ") renamed by " .. getAdminNameForLog(player).." to '" .. tostring(newName).."'" )
end
end
end
end
--end
)
---------------------------------------------------------------------------
--
-- Settings
--
--
--
---------------------------------------------------------------------------
function cacheSettings()
g_Settings = {}
g_Settings.numtimes = getNumber('numtimes',8)
g_Settings.startshow = getBool('startshow',false)
g_Settings.gui_x = getNumber('gui_x',0.56)
g_Settings.gui_y = getNumber('gui_y',0.02)
g_Settings.admingroup = getString("admingroup","Admin")
end
-- React to admin panel changes
addEvent ( "onSettingChange" )
addEventHandler('onSettingChange', g_ResRoot,
function(name, oldvalue, value, playeradmin)
outputDebug( 'MISC', 'Setting changed: ' .. tostring(name) .. ' value:' .. tostring(value) .. ' value:' .. tostring(oldvalue).. ' by:' .. tostring(player and getPlayerName(player) or 'n/a') )
cacheSettings()
-- Update here
if g_SToptimesManager then
g_SToptimesManager.displayTopCount = g_Settings.numtimes
g_SToptimesManager:updateTopText()
end
-- Update clients
clientCall(g_Root,'updateSettings', g_Settings, playeradmin)
end
)
-- New player joined
addEvent('onLoadedAtClient_tt', true)
addEventHandler('onLoadedAtClient_tt', g_Root,
function()
-- Tell newly joined client current settings
clientCall(source,'updateSettings', g_Settings)
-- This could also be the toptimes resource being restarted, so send some mapinfo
local raceInfo = getRaceInfo()
if raceInfo then
triggerClientEvent('onClientSetMapName', source, raceInfo.mapInfo.name )
end
end
)
---------------------------------------------------------------------------
-- Global instance
---------------------------------------------------------------------------
g_SToptimesManager = SToptimesManager:create()
--------------------
--Team Colors Nick--
--------------------
function getPlayerTeamColoredNick(player)
local playerTeam = getPlayerTeam ( player )
if ( playerTeam ) then
local r,g,b = getTeamColor ( playerTeam )
local n1 = toHex(r)
local n2 = toHex(g)
local n3 = toHex(b)
if r <= 16 then n1 = "0"..n1 end
if g <= 16 then n2 = "0"..n2 end
if b <= 16 then n3 = "0"..n3 end
return "#"..n1..""..n2..""..n3..""..getPlayerNametagText(player)
else
return getPlayerNametagText(player)
end
end
function toHex ( n )
local hexnums = {"0","1","2","3","4","5","6","7",
"8","9","A","B","C","D","E","F"}
local str,r = "",n%16
if n-r == 0 then str = hexnums[r+1]
else str = toHex((n-r)/16)..hexnums[r+1] end
return str
end
function someoneReachedHunter(number, sort, model)
if (sort == "vehiclechange" and model == 425) then
triggerClientEvent(source,"setSkyColor",source)
setTimer(outputChatBox,50,1,'#E26161[HUNTER]:#ffffffHunter Has Reached Sky #abcdefChanged#ffffff!', root, 255, 100, 100, true)
end
end
addEvent("onPlayerPickUpRacePickup",true)
addEventHandler("onPlayerPickUpRacePickup",getRootElement(),someoneReachedHunter)