You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

217 lines
6.6 KiB
Lua

-- This generates a json file that contains the position of
-- all the players on your server. It was inspired by the livemap
-- module by "Michael Fitzmayer" <mail@michael-fitzmayer.de>.
-- I rewrote this entire thing from scratch to work with 0.7 and hook
-- into the event handling system in a proper way.
-- My future plans for this will turn it into a nice per-player stream
-- of data, with separate update schedules per player and better
-- on-change handling of information deltas. That will work into
-- a few other ideas that I have with eater.
-- TODO: Configure my editor to output lua formatting that's not shit
jsonInterface = require("jsonInterface")
customEventHooks = require("customEventHooks")
local playerMap = {}
playerMap.map = {}
playerMap.updateInterval = 1000
-- currently unused, it'd be nice to have separate update and flush
-- intervals. That way other events updating things will propagate faster.
playerMap.flushInterval = 1000
playerMap.fileName = "PlayerMap.json"
playerMap.timer = nil
function playerMap.updateAllPlayers()
for pid, player in pairs(Players) do
playerMap.updatePlayer(pid)
end
end
function playerMap.save()
jsonInterface.save(playerMap.fileName, playerMap.map)
end
function playerMap.updateTimer()
playerMap.updateAllPlayers()
playerMap.save()
tes3mp.StartTimer(playerMap.timer)
end
function OnPlayerMapUpdateTick()
playerMap.updateTimer()
end
function playerMap.updatePlayer(pid)
if not playerMap.canUpdatePlayer(pid) then
return
end
local player = Players[pid]
-- Apparently this player did not login or register yet, so we won't
-- plot them.
if playerMap.map[player.accoundName] == nil then
return
end
playerMap.updatePlayerLocation(pid)
playerMap.updatePlayerRotation(pid)
playerMap.map[player.accountName].isOutside = tes3mp.IsInExterior(pid)
playerMap.map[player.accountName].cell = tes3mp.GetCell(pid)
end
function playerMap.canUpdatePlayer(pid)
return Players[pid] ~= nil and Players[pid]:IsLoggedIn()
end
function playerMap.updatePlayerLocation(pid)
playerMap.updatePlayerOutsideLocation(pid)
playerMap.updatePlayerInsideLocation(pid)
playerMap.updatePlayerTransitionLocation(pid)
end
function playerMap.updatePlayerRotation(pid)
playerMap.setPlayerRotation(pid,
tes3mp.GetRotX(pid),
tes3mp.GetRotZ(pid))
end
-- We are outside, this is the happy path
function playerMap.updatePlayerOutsideLocation(pid)
if not tes3mp.IsInExterior(pid) then
return
end
playerMap.setPlayerPosition(pid,
tes3mp.GetPosX(pid),
tes3mp.GetPosY(pid),
tes3mp.GetPosZ(pid))
end
-- We are inside and have just logged in, this is the worst path
function playerMap.updatePlayerInsideLocation(pid)
if tes3mp.IsInExterior(pid) then
return
end
-- Player has already logged in and had a placeholder set
if playerMap.map[Players[pid].accoundName].x ~= nil then
return
end
playerMap.setPlayerPosition(pid,
tes3mp.GetExteriorX(pid),
tes3mp.GetExteriorY(pid),
0) -- cell exteriors have no z position
end
-- We are inside but we were outside recently, so we can set out location to wherever
-- the door was. This is an okay path.
function playerMap.updatePlayerTransitionLocation(pid)
-- Player is outside so we don't need to be here
if tes3mp.IsInExterior(pid) then
return
end
-- Last location was not outside so we can't save whatever overworld position we have anymore
if not playerMap.isOutside(pid) then
return
end
playerMap.setPlayerPosition(pid,
tes3mp.GetPreviousCellPosX(pid),
tes3mp.GetPreviousCellPosY(pid),
tes3mp.GetPreviousCellPosZ(pid))
end
function playerMap.isOutside(pid)
return playerMap.map[Players[pid]].isOutside
end
function playerMap.setPlayerPosition(pid, x, y, z)
local playerName = Players[pid].accoundName
playerMap.map[playerName].x = math.floor( 0.5 + x )
playerMap.map[playerName].y = math.floor( 0.5 + y )
playerMap.map[playerName].z = math.floor( 0.5 + z )
end
function playerMap.setPlayerRotation(pid, rotx, rotz)
local playerName = Players[pid].accoundName
playerMap.map[playerName].rotx = rotx
playerMap.map[playerName].rotz = rotz
local anglenorth = math.deg( rotz )
if anglenorth < 0 then
anglenorth = 360 + anglenorth
end
playerMap.map[playerName].rot = math.floor( 0.5 + anglenorth )
end
-- OnGUIAction is called when you, login or register (and for a bunch of other things besides)
customEventHooks.registerHandler("OnGUIAction", function(eventStatus, pid, idGui, data)
if (idGui ~= guiHelper.ID.LOGIN and idGui ~= guiHelper.ID.REGISTER) then
return nil -- We aren't interested in anything but these two events
end
if data == nil then
return nil -- data is nil when register or login fails
end
local player = Players[pid]
-- I don't really see how this could happen, but best to be safe.
if player == nil then
return
end
playerMap.map[player.accountName] = {}
end)
-- We want to use a validator here so that the player info still exists when this function is called.
-- If we registered a handler, it would be thrown away and all we would know is the pid, which
-- means we would not be able to throw away the player's account name key.
customEventHooks.registerValidator("OnPlayerDisconnect", function(eventStatus, pid)
if Players[pid] == nil or not Players[pid]:IsLoggedIn() then
return nil
end
playerMap.map[Players[pid].accountName] = nil
return nil -- original eventstatus will be used in this case, we're not doing anything amazing
end)
-- When the server starts, we want to start monitoring the players for
customEventHooks.registerHandler("OnServerPostInit", function(eventStatus)
playerMap.timer = tes3mp.CreateTimer("OnPlayerMapUpdateTick", updateInterval)
tes3mp.StartTimer(playerMap.timer)
return nil
end)
-- When the server exists we clear the json file of any entries so the map doesn't
-- "freeze" in a state of having some people on it.
customEventHooks.registerHandler("OnServerExit", function(eventStatus)
playerMap.map = {}
playerMap.save()
-- TODO: Stop the update timer. I suppose it will also get destroyed when
-- we exit so maybe this is fine too.
return nil
end)
-- When the player changes from an exterior to an interior cell, we want to make sure that the last
-- saved coordinates are the ones the player had right before changing cells. This is the door.
customEventHooks.registerHandler("OnPlayerCellChange", function(eventStatus, pid)
playerMap.updatePlayer(pid)
return nil
end)
return playerMap