-- 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" . -- 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