summaryrefslogtreecommitdiff
path: root/server/server.lua
blob: f2119fef9b22a074a3093346cb79f28b18a987fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
local enet = require'enet'
local json = require'dkjson'
local Pos = require'r.pos'
local pairs_except = require'r.pairs_except'
local class = require'r.class'
local rle = require'r.rle'
local noise = require'r.noise'
local common = require 'common'
local SIZE = common.SIZE

local host = enet.host_create('*:'..common.port)
print(host:get_socket_address())
local n = 0 local function next_name() n=n+1  return 'helen'..n end

local function P(amp,scale,seed)
	return {scale=scale,amp=amp,gen=noise.PerlinNoise(seed)} end
local noise_gen = noise.NoiseAgg{ P(0.5,17,3), P(1,10,1), P(0.4,6,2) }

local chunks = common.ChunkMap()
local Chunk = class()
function Chunk.make(cls, cp, d) local self = setmetatable({cp=cp,d=d},cls)
	chunks:add(cp,self) return self end
function Chunk.generate(cls,cp) local offs=cp*SIZE
	local d={} for x=0,SIZE-1 do for y=0,SIZE-1 do
		local nv = noise_gen:at(offs.x+x,offs.y+y) local fill = nv>0.06
		if math.random(3) == 2 then fill = not fill end
		d[1+x*SIZE+y] = (cp.x<0) ~= fill end end
	return cls(cp,d) end
function Chunk.packet(self)
	return json.encode{ pos=self.cp, d=rle.encode(self.d), type='chunk' } end
local function filename(cp) return 'chunk'..cp:key()..'.json' end
function Chunk.load(cls, cp) local f = io.open(filename(cp),'r')
	if not f then return nil end  local j = json.decode(assert(f:read"a"))
	f:close()  return cls(cp,rle.decode(j.d)) end
function Chunk.save(self) local f = io.open(filename(self.cp),'w')
	print('saving',self.cp)
	assert(f:write(json.encode{d=rle.encode(self.d)})) f:close() end
function Chunk.obtain(cls,cp)
	if chunks:get(cp) then return chunks:get(cp) end
	local ch = cls:load(cp)  if ch then return ch end
	return cls:generate(cp) end

local Player = class()
function Player.make(cls, peer, j)
	return setmetatable({peer=peer, pos=Pos(0,0), name=j.name,
		color=j.color, loaded={}}, cls) end
function Player.packet(self,packet_type)
	return json.encode { name=self.name, pos=self.pos, color=self.color, type=packet_type } end
local players = {}
local function find_player(arg) local k,v = next(arg)
	for pl in pairs(players) do if pl[k] == v then return pl end end end
local function other_players(player) return pairs_except(players, player) end
local function send_others(player,data)
	data.from = player.name   local packet = json.encode(data)
	for player2 in other_players(player) do player2.peer:send(packet) end end

local function check(peer,j) -- new player
	if not j.name or #j.name == 0 then return "please enter a name!" end
	if #j.name >16 then return "name too long!" end
	if find_player{name=j.name} then return "name already in use! pick a different one!" end
end

local function greet(player)
	print('greeting',player.name,player.peer)
	players[player] = true
	player.peer:send(player:packet'you')
	for player2 in other_players(player) do
		player.peer:send(player2:packet'player')
		player2.peer:send(player:packet'player') end end

local function doctor_chunks()
	for player in pairs(players) do
		local pcp = player.pos:divmod(SIZE)
		for dx = -1,1 do for dy = -1,1 do local cp = pcp+Pos(dx,dy)
			if not player.loaded[cp:key()] then
				local chunk = Chunk:obtain(cp)
				player.peer:send(chunk:packet())
				player.loaded[cp:key()] = true end end end
		for lcpk in pairs(player.loaded) do local lcp = Pos:unkey(lcpk)
			if (lcp-pcp):linf() > 2 then
				player.peer:send(json.encode{type='unchunk',pos=lcp})
				player.loaded[lcp:key()] = nil end end end
	for k,chunk in pairs(chunks.d) do local cp = Pos:unkey(k)
		local used = false
		for player in pairs(players) do if player.loaded[k] then used=true end end
		if not used then chunk:save() chunks:remove(cp)
			if not next(chunks.d) then print'no chunks' end end end end

while true do
	local ev = host:service(10000)  if ev then
		local peer = ev.peer  local player = find_player{peer=peer}
		if ev.type=='connect' then print('connecting',peer)
		elseif ev.type=='disconnect' then print('disconnecting',peer)
			if player then send_others(player, {type='unplayer'})
				players[player] = nil end
		elseif ev.type=='receive' then local j = json.decode(ev.data)
			local pos  if j.pos then pos = Pos(j.pos.x,j.pos.y) end
				if j.type == 'hi' and not player then local err = check(peer,j)
					if err then peer:send(json.encode{type="error",msg=err}) peer:disconnect_later()
					else greet(Player(peer,j)) end
			elseif j.type == 'move' then player.pos=pos send_others(player, j)
			elseif j.type == 'tile' then chunks:set_tile(pos,j.tile) send_others(player,j)
			elseif j.type == 'chat' then send_others(player,j)
			end
		end
	end
	doctor_chunks()
	io.flush()
end