Spoiler
local Clients = {} -- Stores all info on each client. Clients X/Y is based on their screen cords. use x y for client itself.
local ClientsNo = {}
local Servers = {} -- ID's of all the servers which rebroadcast the game for more range
local Maps = {} -- Stores maps in it in another table. Can host unlimited maps.
local MapInfo = {} -- Stuff that was in the "Info:" This is so if there was a spawn then they spawn there. Or if you need to check so and so.
local Data = {} -- Stores data on each map, Is there a enemy here? A pickup pack?
local ViewMap = {} -- Every players viewable map is in here stored in its own seperate table
local Spawn = {} -- Define the spawn for each map
local DefaultMap = "Default" -- Map everyone first spawns on
local ChangedLines = {} -- Stores lines changed so it can try update the map with min lag
local ClientData = {"ID", "Name", "Map", "X", "Y", "Height", "Width"} -- Just add a bunch of names for each thingy so it knows it exists and can save it
local Explosions = {}
local MapSize = {}
function setDefault(map)
DefaultMap = map
end
function sendClients(Info) -- Send the clients the info. This sends them all it :
for n=1,#Clients do
rednet.send(Clients[n], Info)
end
end
function sendServers(Info) -- Send the servers the info. Sends them all of it.
for n=1,#Servers do
rednet.send(Servers[n], Info)
end
end
function tag(sTag) -- Tags the messages so tagView can use it
return "["..string.gsub(string.gsub(sTag, "%[", "~RightBracket~"), "%]", "~LeftBracket~").."]"
end
function tagView(msg) -- libraryaddict's little thingy ;D
local ReturnMsg = msg
local ReturnTag = {}
if string.find(ReturnMsg, "%[") and string.find(ReturnMsg, "%]") then
while true do
first = string.find(ReturnMsg, "%[", 1)
last = string.find(ReturnMsg, "%]", 2)
if first and last then ReturnTag[#ReturnTag+1] = string.gsub(string.gsub(string.sub(ReturnMsg, first+1, last-1), "~RightBracket~", "%["), "~LeftBracket", "%]")
ReturnMsg = string.sub(ReturnMsg, last+1, string.len(msg))
else
break
end
end
ReturnTag[#ReturnTag+1] = ReturnMsg
return unpack(ReturnTag)
else
return false
end
end
function loadMap(MapName, TableName) -- Loads a map into a table, The info included is loaded into another table
if not MapName then error("function loadMap, Please use loadMap(MapNameAndDir, TableNameYouWantToAccessItWith)") end
if not fs.exists(MapName) then error("Please use a map that exists!") end
Maps[TableName] = {}
local MapLoadVari = io.open(MapName, "r")
local i = 1
local a = 0
local b = 0
local Returns = {}
for line in MapLoadVari:lines() do
if not string.sub(line, 1, 5) == "Info:" then
Map[TableName][i] = line
b = b+1
if string.len(line) > a then
a = string.len(line)
end
else
Returns[#Returns+1] = string.sub(line, 6)
end
i = i+1
end
MapSize[TableName] = {}
MapSize[TableName]["X"] = a
MapSize[TableName]["Y"] = b
loadObjects(TableName)
Returns[#Returns+1] = TableName
return unpack(Returns)
end
function setData(xInfo, yInfo, Map, InfoTable, InfoName, Info) -- Used for map data. Bahahaha confuse my stealers!
local xInfo = tonumber(xInfo)
local yInfo = tonumber(yInfo)
if not Data[Map][yInfo] then
Data[Map][yInfo] = {}
end
if not Data[Map][yInfo][xInfo] then
Data[Map][yInfo][xInfo] = {}
end
if not Data[Map][yInfo][xInfo][InfoTable] then
Data[Map][yInfo][xInfo][InfoTable] = {}
end
Data[Map][yInfo][xInfo][InfoTable][InfoName] = Info
end
function checkData(xInfo, yInfo, Map, InfoTable, InfoName) -- Return data at these cords.
local xInfo = tonumber(xInfo)
local yInfo = tonumber(yInfo)
if InfoName then
if Data[Map][yInfo][xInfo][InfoTable][InfoName] then
return unpack(Data[Map][yInfo][xInfo][InfoTable][InfoName])
end
else
if Data[Map][yInfo][xInfo][InfoTable] then
return unpack(Data[Map][yInfo][xInfo][InfoTable])
end
end
end
function deleteData(xInfo, yInfo, Map, InfoTable)
if Data[Map][yInfo][xInfo][InfoTable] then
Data[Map][yInfo][xInfo][InfoTable] = nil
end
end
function screenOptions(Locked, Width, Height, ID) -- No idea what screenlocked is gonna be good for, Locked must be true or false.
Clients[ClientsNo[ID]]["LockedScreen"] = Locked
Clients[ClientsNo[ID]]["ScreenWidth"] = Width
Clients[ClientsNo[ID]]["ScreenHeight"] = Height
end
function moveScreen(Side, UpDown, ID, Map)
if Clients[ClientsNo[ID]]["Y"]+UpDown >= 1 then
Clients[ClientsNo[ID]]["Y"] = Clients[ClientsNo[ID]]["Y"]+UpDown
elseif Clients[ClientsNo[ID]]["Y"]+UpDown <= MapSize[Map]["Y"] then
Clients[ClientsNo[ID]]["Y"] = Clients[ClientsNo[ID]]["Y"]+UpDown
else -- Wrap screen to edges.
Clients[ClientsNo[ID]]["Y"] = MapSize[Map]["Y"]
end
if Clients[ClientsNo[ID]]["X"]+Side >= 1 then
Clients[ClientsNo[ID]]["X"] = Clients[ClientsNo[ID]]["X"]+Side
elseif Clients[ClientsNo[ID]]["X"]+Side <= MapSize[Map]["X"] then
Clients[ClientsNo[ID]]["X"] = Clients[ClientsNo[ID]]["X"]+Side
else -- wrap
Clients[ClientsNo[ID]]["X"] = MapSize[Map]["X"]
end
draw("all", ID, Map)
end
function setSpawn(MapName, XCord, YCord)
Spawn[MapName] = {}
Spawn[MapName]["X"] = XCord
Spawn[MapName]["Y"] = YCord
end
function playerDataLoad(Name, ID)
if fs.exists("PlayerData/"..Name) then
Clients[#Clients+1] = {}
local PlayerLoad = io.open(Name, "r")
Clients[#Clients] = textutils.unserialize(PlayerLoad.readAll())
ClientsNo[ID] = #Clients
return true
end
return false
end
function playerJoin(ID, Name) -- When they sucessfully join. Assign them info.
if not playerDataLoad(Name, ID) then
ClientsNo[ID] = #Clients+1
Clients[ClientsNo[ID]] = {}
print("h:"..ClientsNo[ID])
Clients[ClientsNo[ID]]["Name"] = Name
Clients[ClientsNo[ID]]["ID"] = ID
setPlayerMap(ID, "OrigSpawn")
end
end
function setPlayerMap(ID, Place, X, Y)
if Place == "OrigSpawn" then
Clients[ClientsNo[ID]]["Map"] = DefaultMap
local x, y = getSpawn(DefaultMap)
Clients[ClientsNo[ID]]["X"] = x
Clients[ClientsNo[ID]]["Y"] = y
else
Clients[ClientsNo[ID]]["Map"] = Place
Clients[ClientsNo[ID]]["X"] = X
Clients[ClientsNo[ID]]["Y"] = Y
end
end
function playerDataSave(Name, ID)
if not fs.exists("PlayerData") then
fs.makeDir("PlayerData")
end
local PlayerSaving = io.open("PlayerData/"..Name, "w")
PlayerSaving:write(textutils.serialize(Clients[ClientsNo[ID]]))
PlayerSaving:close()
end
function split(str, pat)
local t = {} -- NOTE: use {n = 0} in Lua-5.0
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(t,cap)
end
last_end = e+1
s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
cap = str:sub(last_end)
table.insert(t, cap)
end
return unpack(t)
end
function getSpawn(Map) -- Get the point newbies spawn at
return Spawn[Map]["X"], Spawn[Map]["Y"]
end
function draw(Info, ClientNo, Map) -- redraws for the client. Does NOT send the info out! Does NOT edit the map! Well it prob have to send it..
if not ClientNo then error("draw(Info, ClientNo, Map) ClientNo not found. ClientNo is needed") end
if Info == "all" then -- Draw every single appropriate line for the client. They now see the proper screen! Basically forcing a complete redraw. Eg if the screen moved.
ViewMap[ClientNo] = {} -- Reset client map data. They now see a blank screen
for n=1,Clients[ClientNo]["ScreenHeight"] do -- Client number bla bla. This will do it for their screenheight amount
ViewMap[ClientNo][n] = string.sub(Map[Clients[ClientNo]["Y"]+n], Clients[ClientNo]["X"], Clients[ClientNo]["ScreenWidth"])
end
elseif Info == "lines" then -- redraws a single line on their client. This should limit lag/flickering.
for n=1,#ChangedLines do -- Gonna check with all the changed lines. But only the correct ones will be updated.
local Stuff = {split(ChangedLines[n], ":")}
if Clients[ClientNo]["Map"] == Stuff[1] then -- If the changed line is on the same map as the client
--They are the same. You can now check if this client needs to see this line drawn. Or if its outside their screen scope
-- OR I could just redraw it anyways >.>
-- Wait a sec. The ChangedLines should just give the Map:Line:X thats being redrawn.
-- So I need to check if the screen is in the X and Y of the changed.
-- Could define each line like this. MAP:Y:X:X:X:X:X:X
-- So the map got edited on line x. Each of these X's got edited. No way could it all be edited?
-- I think I'll go with that
if tonumber(Stuff[2]) >= Clients[ClientsNo[ID]]["Y"] and tonumber(Stuff[2]) <= Clients[ClientsNo[ID]]["Height"] then
local p = 3
repeat
if tonumber(Stuff[p]) >= Clients[ClientsNo[ID]]["X"] and tonumber(Stuff[p]) <= Clients[ClientsNo[ID]]["Width"] then
--Ok. So you can redraw that line..
ViewMap[ClientNo][n] = string.sub(Map[Clients[ClientNo]["Map"]], Clients[ClientNo]["X"], Clients[ClientNo]["ScreenWidth"])
p = p+1
end
until not Stuff[p]
end
end
end
end
end
function loadObjects(sTableName) -- Define the objects in here to be loaded into the data. This is for stuff like healthpacks etc defined with the map itself.
if not sTableName then error("sTableName dont exist") end
Objects = {"S", "L", "P"} -- Sample! Replace with your own. Letters it finds in the map..
ObjectsData = {"Sample", "Libraryaddict", "Player"}
for n=1,#Maps[sTableName] do -- For lines in that table do
for q=1,string.len(Map[sTableName][n]) do
local sLetter = string.sub(Map[sTableName][n], q, q)
for p=1,#Objects do
if Objects[p] == sLetter then
setData(xInfo, yInfo, sTableName, ObjectsData[p], "Type", ObjectsData[p])
end
end
end
end
end
function editMap(xCord, yCord, Maps, Stuff) -- Edit data shown to player. Aka the map view. thingy.
if Map[Maps][yCord] then
Map[Maps][yCord] = string.sub(Map[Maps][yCord], 1, xCord-1)..Stuff..string.sub(Map[Maps][yCord], xCord+1, string.len(Map[Maps][yCord]))
else error("editMap(), You wanted a yCord thats outside of the maps range.") end
end
function Tick() -- This defines the actions of everything that isnt the player. Eg explosion decay.
spreadExplosion()
MoveFired()
MoveNPC()
TheTick = os.startTimer(0.1)
end
function createExplosion(xCord, yCord, MapName, Spread, SpreadRate, DecayRate, Characters, ExplosionName, ...) -- All of this is needed. The "..." is directions. Use left, right, up, down if you want it to act like a normal explosion. ExplosionName is the kind of explosion this is, Eg. a nuke.
--DataName is what you will see when you check Data
setData(xInfo, yInfo, MapName, "Explosion", "Type", ExplosionName)
local eNum = #Explosions+1
Explosions[eNum] = {}
local dirs = {...}
local function def(sName, sData)
Explosions[eNum][sName] = sData
end
for n=1,#dirs do
dirs[n] = string.lower(dirs[n])
if n == 5 then
error("Why are there 5 sides? error at createExplosion()")
end
end
for n=1,#dirs do
if dirs[n] == "up" then
def("Up", true)
elseif dirs[n] == "down" then
def("Down", true)
elseif dirs[n] == "left" then
def("Left", true)
elseif dirs[n] == "right" then
def("Right", true)
end
end
def("Map", MapName)
def("X", xCord)
def("Y", yCord)
def("Spread", Spread)
def("Rate", SpreadRate)
def("Decay", DecayRate)
def("Char", Character)
def("Name", ExplosionName)
def("STimer", 0)
def("DTimer", DecayRate)
setData(Explosions[EN]["X"], Explosions[EN]["Y"], Explosions[EN]["Map"], "Explosion", "Type", "Explosion")
doExplosion(eNum)
end
--Add dirs! This is check for when I get back
function spreadExplosion() -- Finds all explosions and calls them. Pretty basic function but eh. I should stick this in Tick()
for n=1,#Explosions do
doExplosion(n)
end
end
function deleteAllData(xInfo, yInfo, Map) -- Deletes all data there. Good for rewriting it :
Data[Map][yInfo][xInfo] = {}
end
function isSolid(xInfo, yInfo, Map)
if checkData(xInfo, yInfo, Map, "Solid", "Solid") == "Solid" then
return true
else
return false
end
end
function setSolid(xInfo, yInfo, Map)
setData(xInfo, yInfo, Map, "Solid", "Solid", "Solid")
end
function unsetSolid(xInfo, yInfo, Map)
setData(xInfo, yInfo, Map, "Solid", "Solid", "Not Solid")
end
function returnSide(xCord, yCord, Side)
local Side = string.lower(Side)
if Side == "left" then
return (xCord-1), yCord
elseif Side == "right" then
return (xCord+1), yCord
elseif Side == "up" then
return xCord, (yCord-1)
elseif Side == "down" then
return xCord, (yCord+1)
end
end
function returnExplodeData(EN)
local KO = {}
if Explosions[EN] then
for n,a in ipairs(Explosions[EN]) do
KO[n] = a
end
end
return unpack(KO)
end
function returnMap(ID)
return unpack(ViewMap[ID])
end
--[[function doExplosion(EN) -- EN = ExplosionsNumber. This function auctally does the explosion checking.
Explosions[EN]["STimer"] = Explosions[EN]["STimer"]-1
Explosions[EN]["DTimer"] = Explosions[EN]["DTimer"]-1
if Explosions[EN]["DTimer"] == 0 then -- Delete this one
deleteData(Explosions[EN]["X"], Explosions[EN]["Y"], Explosions[EN]["Map"], "Explosion")
table.remove(Explosions, EN)
return
end -- checkData(xInfo, yInfo, Map, InfoTable, InfoName)
if Explosions[EN]["STimer"] <= 0 then--Spread
Explosions[EN]["STimer"] = Explosions[EN]["SpreadRate"]
local Side = {Explosions[EN]["Up"], Explosions[EN]["Down"], Explosions[EN]["Left"], Explosions[EN]["Right"]}
local Sides = {Explosions[EN]["Up"], Explosions[EN]["Down"], Explosions[EN]["Left"], Explosions[EN]["Right"]}
for n=1,Side do
local PigSnout = math.random(1,#Side) -- function createExplosion(xCord, yCord, MapName, Spread, SpreadRate, DecayRate, Characters, ExplosionName, ...) -- All of this is needed. The "..." is directions. Use left, right, up, down if you want it to act like a normal explosion. ExplosionName is the kind of explosion this is, Eg. a nuke.
if isSolid(returnSide(xInfo, yInfo, Side[PigSnout]), Map) == false then
doExplosion(returnSide(xInfo, yInfo, Side[PigSnout])
table.remove(Side, PigSnout)
end
end
end
end
end]]
What I used to test it. Minus the API loading. map is the API.