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 Player=require'player'.Player local posix_time = require"posix.time" local posix_signal = require"posix.signal" local db = require'db' math.randomseed(os.time()) local host = enet.host_create("*:8473",nil,2) print(host:get_socket_address()) -- sequential list of all players local playerlist = {} -- 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 player_join_packet(player) return json.encode{t="join",pl=player:info_part()} end local function player_you_packet(player) return json.encode{t="you",pl=player:info_part()} 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() -- set of peers currently within handshake procedure local connecting_peers = {} local function create_player(...) -- advances connecting peer to player local player = Player:make(...) table.insert(playerlist, player) connecting_peers[player.peer] = nil player.peer:send(player_you_packet(player)) for i,otherplayer in ipairs(playerlist) do if otherplayer ~= player then player.peer:send(player_join_packet(otherplayer)) otherplayer.peer:send(player_join_packet(player)) end end print("connect",player.id,player.peer) end local function connecting_peer_ev() local ev = coroutine.yield() print("hello") local j = json.decode(ev.data) local op = j.t if op == "handshake" then print(ev,'handshake; username',j.username) create_player(ev.peer,j.username) else error("weird handshake: "..op) end end local function on_peer_connect(peer) print("peer connect: ",peer) local co = coroutine.create(connecting_peer_ev) assert(coroutine.resume(co)) connecting_peers[peer] = co end local function on_peer_disconnect(peer) print("peer disconnect: ",peer) connecting_peers[peer] = nil local player,idx = player_by_peer(peer) if player then table.remove(playerlist, idx) for i,otherplayer in ipairs(playerlist) do otherplayer.peer:send(player_leave_packet(player)) end print("disconnect", player.id, player.peer) end end local function handle_player_packet(player,ev) print('player packet ',player,ev.data) 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 local function handle_ev(ev) if ev.type == 'connect' then on_peer_connect(ev.peer) elseif ev.type == 'disconnect' then on_peer_disconnect(ev.peer) elseif ev.type == 'receive' then print("recv:",ev.peer,ev.data) if connecting_peers[ev.peer] then local co = connecting_peers[ev.peer] print("\t-> hshake",co,coroutine.status(co)) local ok,err = coroutine.resume(co, ev) if not ok then print("hshake error: ",ev.peer,err) connecting_peers[ev.peer] = nil ev.peer:disconnect_now() end else local player = player_by_peer(ev.peer) if not player then error("packet from unknown peer ",ev.peer) end print("\t-> player",player) handle_player_packet(player, ev) 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 local tick_interval = 1 -- seconds local last_tick_time = timenow() 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() local txn = db.txn(true) for cp,ch in map:iter_chunks() do map:save_chunk(cp,txn) if not player_near_chunk(cp) then -- print("unloading chunk",cp) map:remove_chunk(cp) end end txn:commit() 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