summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorubq323 <ubq323@ubq323.website>2023-02-04 23:03:19 +0000
committerubq323 <ubq323@ubq323.website>2023-02-04 23:03:19 +0000
commit0dc1276df57aa16b4f0eaecf54fb5cd8f00115c6 (patch)
tree0d5672f6f05f56022ed834ad35c1c2b2df52c21c
parent1ebd7d9b7b62c8e05d527611a1944ed1a876b890 (diff)
many many optimizations and refactorings; introduction of Map to support multiple chunks, modify worldgen and client drawing to support multiple chunks
-rw-r--r--client/drawing.lua44
-rw-r--r--client/main.lua53
-rw-r--r--common/chunk.lua23
-rw-r--r--common/class.lua7
-rw-r--r--common/constants.lua4
-rw-r--r--common/coords.lua77
-rw-r--r--common/map.lua51
-rw-r--r--server/server.lua55
-rw-r--r--server/worldgen.lua49
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
+}