diff options
-rw-r--r-- | client/drawing.lua | 44 | ||||
-rw-r--r-- | client/main.lua | 53 | ||||
-rw-r--r-- | common/chunk.lua | 23 | ||||
-rw-r--r-- | common/class.lua | 7 | ||||
-rw-r--r-- | common/constants.lua | 4 | ||||
-rw-r--r-- | common/coords.lua | 77 | ||||
-rw-r--r-- | common/map.lua | 51 | ||||
-rw-r--r-- | server/server.lua | 55 | ||||
-rw-r--r-- | server/worldgen.lua | 49 |
9 files changed, 269 insertions, 94 deletions
diff --git a/client/drawing.lua b/client/drawing.lua index bbcf03a..5334a93 100644 --- a/client/drawing.lua +++ b/client/drawing.lua @@ -1,6 +1,7 @@ local coords=require"common.coords" local Pos = coords.Pos local chunk = require"common.chunk" +local CHUNK_SIZE = require"common.constants".CHUNK_SIZE local tau=math.pi*2 @@ -38,20 +39,24 @@ local colors = { c(192,0,192), -- purple c(128,128,128), -- grey } - - +-- precompute sins and cosines +local _coss,_sins = {},{} +for i=0,5 do + local angle = tau*(i+0.5)/6 + _coss[i+1] = math.cos(angle) + _sins[i+1] = math.sin(angle) +end -local zthr0 = 2 -local zthr1 = 5 +local zthr0 = 2.7 +local zthr1 = 6 local _corners = {} local function draw_hex(cpos,color,zoom) local cx,cy = cpos.x,cpos.y - for i=0,5 do - local angle = tau*(i+0.5)/6 - local x = cx + math.cos(angle) - local y = cy + math.sin(angle) - _corners[2*i+1] = x - _corners[2*i+2] = y + for i=1,6 do + local x = cx + _coss[i] + local y = cy + _sins[i] + _corners[2*i-1] = x + _corners[2*i ] = y end -- love.graphics.setColor(love.math.colorFromBytes(0xe7,0x9e,0)) love.graphics.setColor(color or {0.91,0.62,0}) @@ -63,22 +68,27 @@ local function draw_hex(cpos,color,zoom) end end -local function draw_chunk(camera,the_chunk) +local function draw_map(camera,map) local tl,br = camera:extents() local tlh,brh = tl:to_hex():round(), br:to_hex():round() local trh = coords.Pos:make(br.x,tl.y):to_hex():round() + local _h = coords.Hex:new() + local _p = coords.Pos:new() + for r = tlh.r-1,brh.r+1 do local rowidx = r-tlh.r local minq = tlh.q - math.floor((rowidx+1)/2) local maxq = minq+(trh.q-tlh.q)+1 for q = minq,maxq do - local h = coords.Hex:make(q,r) - local t = the_chunk:at(h) + -- local h = coords.Hex:make(q,r) + _h:init(q,r) + _h:to_pos(_p) + local t = map:at(_h) if type(t) == "number" then - draw_hex(h:to_pos(),colors[t],camera.zoom) + draw_hex(_p,colors[t],camera.zoom) elseif t then - draw_hex(h:to_pos(),nil,camera.zoom) + draw_hex(_p,nil,camera.zoom) end end end @@ -87,7 +97,7 @@ local function draw_chunk(camera,the_chunk) love.graphics.setColor(0,1,0) local function p(q,r) return coords.Hex:make(q,r):to_pos() end - local h = chunk.SIZE-0.5 + local h = CHUNK_SIZE-0.5 local c00 = p(-0.5,-0.5) local c01 = p(-0.5,h) local c10 = p(h,-0.5) @@ -100,4 +110,4 @@ local function draw_chunk(camera,the_chunk) end -return {draw_hex=draw_hex,draw_chunk=draw_chunk} +return {draw_hex=draw_hex,draw_map=draw_map} diff --git a/client/main.lua b/client/main.lua index 7af1bd4..da1b91d 100644 --- a/client/main.lua +++ b/client/main.lua @@ -13,6 +13,7 @@ local Pos = coords.Pos local camera = require"camera".Camera:make() local Chunk = require"common.chunk".Chunk local util = require"util" +local Map = require"common.map".Map -- local pprint=require"common.pprint" -- pprint.setup{show_all=true} @@ -20,9 +21,9 @@ local util = require"util" math.randomseed(os.time()) -local host,peer +local map = Map:make() -local chunk +local host,peer _G.debugmode = false @@ -70,7 +71,7 @@ local function update_local_player(pl,dt) end -local function sync_local_player(pl,peer) +local function sync_local_player(pl) -- send updated info about local player to server if pl.pos_dirty then peer:send(json.encode{t="ppos",x=pl.pos.x,y=pl.pos.y}) @@ -81,26 +82,39 @@ local function send_settile(hpos,tile) peer:send(json.encode{t="settile",q=hpos.q,r=hpos.r,tile=tile}) end + +-- hack +local time_since_last_chunkreq = 100 function love.update(dt) + time_since_last_chunkreq = time_since_last_chunkreq + dt if local_player then update_local_player(local_player,dt) if love.keyboard.isScancodeDown"q" then camera.zoom = camera.zoom*1.05 end if love.keyboard.isScancodeDown"e" then camera.zoom = camera.zoom/1.05 end camera.zoom = math.max(2.25,math.min(50,camera.zoom)) - sync_local_player(local_player,peer) + sync_local_player(local_player) + end + + local mh = camera:screen_to_world(Pos:make(love.mouse.getPosition())):to_hex():round() + if false== map:at(mh) and love.mouse.isDown(1) then + print("place at",mh) + map:set_at(mh,true) + -- print(mh,true) + send_settile(mh,true) + elseif map:at(mh) and love.mouse.isDown(2) then + map:set_at(mh,false) + -- print(mh,false) + send_settile(mh,false) end - if chunk then - local mh = camera:screen_to_world(Pos:make(love.mouse.getPosition())):to_hex():round() - if false== chunk:at(mh) and love.mouse.isDown(1) then - chunk:set_at(mh,true) - -- print(mh,true) - send_settile(mh,true) - elseif chunk:at(mh) and love.mouse.isDown(2) then - chunk:set_at(mh,false) - -- print(mh,false) - send_settile(mh,false) + + if local_player then + local player_cp = local_player.pos:to_hex():containing_chunk() + if map:chunk(player_cp) == nil and time_since_last_chunkreq > 1 then + time_since_last_chunkreq = 0 + peer:send(json.encode{t="reqchunk",u=player_cp.u,v=player_cp.v}) end end + repeat local ev = host:service() @@ -123,10 +137,11 @@ function love.update(dt) local pl = j.pl local_player = {pos=coords.Pos:make(pl.x,pl.y),color=pl.color,id=pl.id} elseif op == "chunk" then - chunk = Chunk.from_packet_data(j) + local ch = Chunk.from_packet_data(j) + map:add_chunk(ch.cp,ch) elseif op == "settile" then local h = coords.Hex:make(j.q,j.r) - chunk:set_at(h,j.tile) + map:set_at(h,j.tile) end end until not ev @@ -140,9 +155,7 @@ function love.draw() end camera:apply_trans() - if chunk then - drawing.draw_chunk(camera,chunk) - end + drawing.draw_map(camera,map) if local_player then draw_player(local_player,true) @@ -169,6 +182,8 @@ function love.draw() "ph "..tostring(local_player.pos:to_hex()).." "..tostring(local_player.pos:to_hex():round()), "-", "voob "..tostring(camera.zoom), + "-", + "fps "..tostring(love.timer.getFPS()), },10,10) end end diff --git a/common/chunk.lua b/common/chunk.lua index 1d3faa6..affdab8 100644 --- a/common/chunk.lua +++ b/common/chunk.lua @@ -1,9 +1,8 @@ local json = require"common.dkjson" local class = require"common.class" +local coords = require"common.coords" -local CHUNK_SIZE = 64 - --- for now tiles shall be booleans +local CHUNK_SIZE = require"common.constants".CHUNK_SIZE local function index_ok(offq,offr) return 0<=offq and 0<=offr and offq<CHUNK_SIZE and offr<CHUNK_SIZE @@ -17,12 +16,15 @@ local function index(offq,offr) end local Chunk = class() -function Chunk.make(cls,tiles) - return setmetatable({tiles=tiles},cls) +function Chunk.make(cls,cp,tiles) + return setmetatable({cp=cp,tiles=tiles},cls) end function Chunk.at(self,hoffs) - if not index_ok(hoffs.q,hoffs.r) then return nil end - return self.tiles[index(hoffs.q,hoffs.r)] + return self:_atqr(hoffs.q,hoffs.r) +end +function Chunk._atqr(self,q,r) + if not index_ok(q,r) then return nil end + return self.tiles[index(q,r)] end function Chunk.set_at(self,hoffs,tile) if not index_ok(hoffs.q,hoffs.r) then return end @@ -30,17 +32,18 @@ function Chunk.set_at(self,hoffs,tile) end function Chunk.data_packet(self) - return json.encode{t="chunk",tiles=self.tiles} + return json.encode{t="chunk",tiles=self.tiles,u=self.cp.u,v=self.cp.v} end function Chunk.from_packet_data(packet) -- assuming packet has already been json.decoded -- since otherwise how would we know it's a chunk packet - return Chunk:make(packet.tiles) + + local cp = coords.ChunkPos:make(packet.u,packet.v) + return Chunk:make(cp,packet.tiles) end return { Chunk=Chunk, - SIZE=CHUNK_SIZE, index=index, index_ok=index_ok, } diff --git a/common/class.lua b/common/class.lua index 37cf7bd..f5cd46e 100644 --- a/common/class.lua +++ b/common/class.lua @@ -1,3 +1,9 @@ +-- currently a class is a table T with T.__index = T +-- then to make an instance of this class, we do setmetatable(instance,T) +-- this should be fine for anything we wish to do. it is possible we will eventually +-- split this into two separate tables though, perhaps? i don't see why we would ever +-- do this though. + local function class() local T = {} T.__index = T @@ -13,6 +19,7 @@ local function extend(Base) end end setmetatable(T,{__index=Base}) + return T end return setmetatable({ diff --git a/common/constants.lua b/common/constants.lua new file mode 100644 index 0000000..bf1bb7a --- /dev/null +++ b/common/constants.lua @@ -0,0 +1,4 @@ +-- to avoid some circular dependencies +return { + CHUNK_SIZE=64, +} diff --git a/common/coords.lua b/common/coords.lua index 351aa53..f0ca877 100644 --- a/common/coords.lua +++ b/common/coords.lua @@ -1,4 +1,5 @@ local class = require"common.class" +local CHUNK_SIZE = require"common.constants".CHUNK_SIZE -- Hex: q,r,s. invariant that q+r+s=0 -- add, subtract @@ -13,15 +14,24 @@ local function round(x) end end -local Pos, Hex +local Pos, Hex, ChunkPos local SR3 = math.sqrt(3) -local Hex = class() -function Hex.make(cls,q,r,s) +Hex = class() +function Hex.new(cls) + return setmetatable({},cls) +end +function Hex.init(self,q,r,s) s=s or -q-r assert(q+r+s==0,"hex coord doesn't meet invariant") - return setmetatable({q=q,r=r,s=s},cls) + self.q=q + self.r=r + self.s=s + return self +end +function Hex.make(cls,...) + return cls:new():init(...) end function Hex.round(self) -- return a new Hex rounded to integer coordinates @@ -44,20 +54,43 @@ function Hex.round(self) return Hex:make(rq,rr,rs) end -function Hex.to_pos(self) +function Hex.to_pos(self,into) + into = into or Pos:new() local x = self.q*SR3 + self.r*(SR3/2) local y = self.r*(3/2) - return Pos:make(x,y) + return into:init(x,y) end +function Hex.containing_chunk(self) + local u = math.floor(self.q/CHUNK_SIZE) + local v = math.floor(self.r/CHUNK_SIZE) + return ChunkPos:make(u,v) +end +function Hex.chunk_and_offset(self) + local cp = self:containing_chunk() + local tl = cp:extents() + return cp, (self-tl) +end +function Hex.offset_in_chunk(self) + local cp,offs = self:chunk_and_offset() + return offs +end function Hex.__add(self,other) return Hex:make(self.q+other.q, self.r+other.r, self.s+other.s) end function Hex.__sub(self,other) return Hex:make(self.q-other.q, self.r-other.r, self.s-other.s) end function Hex.__tostring(self) return string.format("H(%.2f,%.2f)",self.q,self.r) end - +function Hex.__eq(a,b) return a.q==b.q and a.r==b.r end Pos = class() -function Pos.make(cls,x,y) - return setmetatable({x=x,y=y},cls) +function Pos.new(cls) + return setmetatable({},cls) +end +function Pos.init(self,x,y) + self.x = x + self.y = y + return self +end +function Pos.make(cls,...) + return cls:new():init(...) end function Pos.__add(self,other) return Pos:make(self.x+other.x,self.y+other.y) end function Pos.__sub(self,other) return Pos:make(self.x-other.x,self.y-other.y) end @@ -74,19 +107,39 @@ function Pos.__div(a,b) assert(type(b) == "number","can only divide Pos by scalar, and can't divide scalar by Pos") return a*(1/b) end +function Pos.__eq(a,b) return a.x==b.x and a.y==b.y end function Pos.lensq(self) return self.x^2 + self.y^2 end function Pos.len(self) return math.sqrt(self:lensq()) end function Pos.norm(self) return self/self:len() end function Pos.dot(self,other) return self.x*other.x + self.y*other.y end -function Pos.to_hex(self) +function Pos.to_hex(self,into) + into = into or Hex:new() local q = self.x*(SR3/3) - self.y*(1/3) local r = (2/3)*self.y - return Hex:make(q,r,-q-r) + return into:init(q,r,-q-r) end function Pos.__tostring(self) return string.format("(%.2f,%.2f)",self.x,self.y) end +-- represents coordinates of a chunk +-- ie pair of integers. the chunk at spawn is C(0,0), the one to the right of that is C(1,0), etc +ChunkPos = class() +function ChunkPos.make(cls,u,v) + return setmetatable({u=u,v=v},cls) +end +function ChunkPos.__add(self,other) return ChunkPos:make(self.u+other.u,self.v+other.v) end +function ChunkPos.__sub(self,other) return ChunkPos:make(self.u-other.u,self.v-other.v) end +function ChunkPos.__tostring(self) return string.format("C(%d,%d)",self.u,self.v) end +function ChunkPos.__eq(a,b) return a.u==b.u and a.v==b.v end +function ChunkPos.extents(self) + -- returns Hex of topleft and bottomright + local tlq,tlr = self.u*CHUNK_SIZE, self.v*CHUNK_SIZE + local brq,brr = (self.u+1)*CHUNK_SIZE -1, (self.v+1)*CHUNK_SIZE -1 + return Hex:make(tlq,tlr), Hex:make(brq,brr) +end + + -return {Hex=Hex,Pos=Pos} +return {Hex=Hex,Pos=Pos,ChunkPos=ChunkPos} diff --git a/common/map.lua b/common/map.lua index fe4b9e0..5bcd09e 100644 --- a/common/map.lua +++ b/common/map.lua @@ -3,4 +3,55 @@ -- the specifics of which are then implemented separately for client and server -- it will probably also do things relating to entities and multiblock things +local class = require"common.class" +local chunk = require"common.chunk" +local CHUNK_SIZE = require"common.constants".CHUNK_SIZE +local Map = class() +function Map.make(cls) + return setmetatable({chunks={}},cls) +end +function Map.add_chunk(self,cp,the_chunk) + print("adding chunk",cp) + assert(the_chunk.cp == cp,"attempting to add chunk with the wrong cp") + if not self.chunks[cp.u] then self.chunks[cp.u] = {} end + self.chunks[cp.u][cp.v] = the_chunk +end +function Map.unload_chunk(self,cp) + if not self.chunks[cp.u] then return end + self.chunks[cp.u][cp.v] = nil + -- remove list if empty + if next(self.chunks[cp.u]) == nil then self.chunks[cp.u] = nil end +end +function Map.chunk(self,cp) + -- return chunk at chunk coord cp + -- if that chunk isn't loaded return nil + return self:_chunkuv(cp.u,cp.v) +end +function Map._chunkuv(self,u,v) + -- same as above but with numbers instead of objects + -- to avoid allocations inside loop + return self.chunks[u] and self.chunks[u][v] +end +function Map.at(self,hpos) + -- returns tile at world coord hpos + -- if that tile's containing chunk isn't loaded, return nil + + -- not using the methods for doing this, in order to avoid lots of allocations + -- inside the main drawing loop + local cpu,cpv = math.floor(hpos.q/CHUNK_SIZE),math.floor(hpos.r/CHUNK_SIZE) + local hoffq,hoffr = hpos.q-(cpu*CHUNK_SIZE), hpos.r-(cpv*CHUNK_SIZE) + local ch = self:_chunkuv(cpu,cpv) + if ch == nil then return nil end + return ch:_atqr(hoffq,hoffr) +end +function Map.set_at(self,hpos,tile) + local cp,hoffs = hpos:chunk_and_offset() + local ch = self:chunk(cp) + -- setting a tile in an unloaded chunk is silently ignored + -- this might change one day + if ch == nil then return nil end + ch:set_at(hoffs,tile) +end + +return {Map=Map} diff --git a/server/server.lua b/server/server.lua index 08f75c0..a7ee04b 100644 --- a/server/server.lua +++ b/server/server.lua @@ -3,8 +3,9 @@ local json = require"common.dkjson" local chunk = require"common.chunk" local Chunk = require"common.chunk".Chunk local noise = require"noise" -local unpack = unpack or table.unpack local coords = require"common.coords" +local worldgen = require"worldgen" +local Map = require"common.map".Map math.randomseed(os.time()) @@ -61,40 +62,15 @@ local function player_move_packet(player,x,y) end --- worldgen -local the_tiles = {} -local function p(amp,scale) return {scale=scale,amp=amp,gen=noise.PerlinNoise:make()} end -local ng = noise.NoiseAgg:make{ - p(1,20), - -- p(0.7,2), - p(0.5,15), - --p(2,200), -} -local ng2 = noise.NoiseAgg:make{p(1,20),p(0.5,15)} - -for q = 0,chunk.SIZE-1 do - for r = 0,chunk.SIZE-1 do - local p = coords.Hex:make(q,r):to_pos() - local ix = chunk.index(q,r) - local nv = ng:at(p.x,p.y) - if nv <= 0 then - the_tiles[ix] = false - else - local nv2 = ng2:at(p.x,p.y) - nv2 = math.max(-0.9999999,math.min(0.9999999,nv2*2.5)) - nv2 = (nv2+1)/2 - print(nv2) - - local tv = 1+math.floor(nv2*8) - assert(1<=tv and tv<=8,"oopsy woopsy") - the_tiles[ix] = tv - end +local map = Map:make() +local function get_or_gen_chunk(cp) + local ch = map:chunk(cp) + if not ch then + ch = worldgen.gen_chunk(cp) + map:add_chunk(cp,ch) end + return ch end -local the_chunk = Chunk:make(the_tiles) -print"generated chunk" - - while true do local ev = host:service(100) @@ -104,7 +80,8 @@ while true do table.insert(playerlist,player) print("connect",player.peer,player.id) player.peer:send(player_you_packet(player)) - player.peer:send(the_chunk:data_packet()) + local central_chunk = get_or_gen_chunk(coords.ChunkPos:make(0,0)) + player.peer:send(central_chunk:data_packet()) for i,otherplayer in ipairs(playerlist) do if otherplayer ~= player then @@ -139,8 +116,8 @@ while true do end elseif op == "settile" then local h = coords.Hex:make(j.q,j.r) - the_chunk:set_at(h,j.tile) - -- print(player.id,"settile",h,j.tile) + 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 @@ -149,6 +126,12 @@ while true do 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 = get_or_gen_chunk(cp) + player.peer:send(ch:data_packet()) end end diff --git a/server/worldgen.lua b/server/worldgen.lua new file mode 100644 index 0000000..5d496a0 --- /dev/null +++ b/server/worldgen.lua @@ -0,0 +1,49 @@ +local coords = require"common.coords" +local noise = require"noise" +local chunk = require"common.chunk" +local CHUNK_SIZE = require"common.constants".CHUNK_SIZE + +local function p(amp,scale) return {scale=scale,amp=amp,gen=noise.PerlinNoise:make()} end +-- local ng = noise.NoiseAgg:make{ +-- p(1,20), +-- -- p(0.7,2), +-- p(0.5,15), +-- --p(2,200), +-- } + +-- whether there is a tile there or not +local surface_ng = noise.NoiseAgg:make{p(1,20),p(0.5,15)} +-- if there is a tile there, what color should it be +local color_ng = noise.NoiseAgg:make{p(1,20),p(0.5,15)} + + +local function gen_chunk(chpos) + local htl,hbr = chpos:extents() + local tiles = {} + + for q = 0,CHUNK_SIZE-1 do + for r = 0,CHUNK_SIZE-1 do + local p = (htl+coords.Hex:make(q,r)):to_pos() + local ix = chunk.index(q,r) + local nv = surface_ng:at(p.x,p.y) + if nv <= 0 then + tiles[ix] = false + else + local nv2 = color_ng:at(p.x,p.y) + nv2 = math.max(-0.9999999,math.min(0.9999999,nv2*2.5)) + nv2 = (nv2+1)/2 + local tv = 1+math.floor(nv2*8) + assert(1<=tv and tv<=8,"oopsy woopsy") + tiles[ix] = tv + end + end + end + + local the_chunk = chunk.Chunk:make(chpos,tiles) + return the_chunk +end + + +return { + gen_chunk=gen_chunk +} |