local enet = require"enet" local json = require"common.dkjson" local chunk = require"common.chunk" local noise = require"noise" local coords = require"common.coords" local Pos = coords.Pos local worldgen = require"worldgen" local MapS = require"map".MapS local posix_time = require"posix.time" local posix_signal = require"posix.signal" math.randomseed(os.time()) local host = enet.host_create("*:8473",nil,2) print(host:get_socket_address()) -- sequential list of all players local playerlist = {} local nextid = 1 -- this is maybe suboptimal -- but it is simplest for now local function player_by_id(id) for i,pl in ipairs(playerlist) do if pl.id == id then return pl, i end end return nil end local function player_by_peer(peer) for i,pl in ipairs(playerlist) do if pl.peer == peer then return pl, i end end return nil end local function random_color() return {math.random(),math.random(),math.random()} end local function make_player(peer) local p = {pos=Pos:make(0,0),color=random_color(),peer=peer,id=nextid} nextid = nextid + 1 return p end local function player_info_part(player) return { id=player.id, x=player.pos.x, y=player.pos.y, color=player.color, } end local function player_join_packet(player) return json.encode{t="join",pl=player_info_part(player)} end local function player_you_packet(player) return json.encode{t="you",pl=player_info_part(player)} end local function player_leave_packet(player) return json.encode{t="leave",id=player.id} end local function player_move_packet(player,x,y) return json.encode{t="move",id=player.id,x=x,y=y} end local function chat_packet(fromplayer,msg) return json.encode{t="chat",from=fromplayer.id,msg=msg} end local map = MapS:make() local function handle_ev(ev) -- handle network event if ev.type == "connect" then local player = make_player(ev.peer) table.insert(playerlist,player) print("connect",player.peer,player.id) player.peer:send(player_you_packet(player)) local central_chunk = map:obtain(coords.ChunkPos:make(0,0)) player.peer:send(central_chunk:data_packet()) for i,otherplayer in ipairs(playerlist) do if otherplayer ~= player then -- tell new player about each other player player.peer:send(player_join_packet(otherplayer)) -- tell each other player about new player otherplayer.peer:send(player_join_packet(player)) end end elseif ev.type == "disconnect" then local player, idx = player_by_peer(ev.peer) if not player then error("sneeze "..ev.peer) end print("disconnect",player.peer,player.id) table.remove(playerlist,idx) for i,otherplayer in ipairs(playerlist) do otherplayer.peer:send(player_leave_packet(player)) end elseif ev.type == "receive" then local player = player_by_peer(ev.peer) if not player then error("sneezey "..ev.peer) end local j = json.decode(ev.data) -- print(ev.channel,ev.data) local op = j.t if op == "ppos" then local x,y = j.x,j.y player.pos = coords.Pos:make(x,y) -- print(player.id,"-->",player.pos[1],player.pos[2]) for i,otherplayer in ipairs(playerlist) do if otherplayer ~= player then otherplayer.peer:send(player_move_packet(player,x,y),1) end end elseif op == "settile" then local h = coords.Hex:make(j.q,j.r) map:set_at(h,j.tile) -- print(player.id,"settile",h,j.tile) for i,otherplayer in ipairs(playerlist) do if otherplayer ~= player then -- same packet structure s2c as c2s -- when multiple chunks exist and players only get info -- about stuff near to them, that won't be the case any more otherplayer.peer:send(ev.data) end end elseif op == "reqchunk" then -- i am not certain this is the best way for this to work -- i might change it later local cp = coords.ChunkPos:make(j.u,j.v) local ch = map:obtain(cp) player.peer:send(ch:data_packet()) elseif op == "chat" then print("chat ["..player.id.."] "..j.msg) for i,otherplayer in ipairs(playerlist) do otherplayer.peer:send(chat_packet(player,j.msg)) end end end end local function timenow() -- monotonic clock, guaranteed to never go backwards local tv = posix_time.clock_gettime(posix_time.CLOCK_MONOTONIC) -- this discards some precision but i don't care return tv.tv_sec + (tv.tv_nsec/1000000000) end -- do something only every x seconds -- returns function(dt) -> bool -- which returns true if you should, and false otherwise local function every(interval) local time_since = interval+100 return function(dt) time_since = time_since + dt if time_since > interval then time_since = 0 return true else return false end end end local tick_interval = 1 -- seconds local last_tick_time = timenow() -- average tps calculation local dts = {} local ndt = 1 local ndts = 100 local ntick = 0 local function player_near_chunk(cp) -- true: chunk at cp should stay loaded (because of a nearby player), -- false: no players nearby, can be unloaded -- this is maybe not as efficient as it possibly could be for _,player in ipairs(playerlist) do local pcp = player.pos:to_hex():containing_chunk() for _,neighb in ipairs(pcp:neighborhood()) do if neighb == cp then return true end end end return false end local function save_things() for cp,ch in map:iter_chunks() do map:save_chunk(cp) if not player_near_chunk(cp) then -- print("unloading chunk",cp) map:remove_chunk(cp) end end end local function tick(ntick) if ntick % 30 == 0 then -- print("saving things") save_things() end end local stopping=false posix_signal.signal(posix_signal.SIGINT, function() stopping=true end) while true do if stopping then print("stopping...") save_things() break end local now = timenow() local dt = now - last_tick_time local time_till_next_tick = (last_tick_time+tick_interval)-now if time_till_next_tick > 0.001 then local ev = host:service(time_till_next_tick*1000) if ev then handle_ev(ev) end else last_tick_time = now ntick = ntick + 1 tick(ntick) end end