From dd12ceb233aa44101f819700d5467d8ba4d2fb4e Mon Sep 17 00:00:00 2001
From: ubq323 <ubq323@ubq323.website>
Date: Wed, 15 Mar 2023 00:31:41 +0000
Subject: add scene system, move top level stuff into its own module

---
 client/game.lua | 357 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 client/main.lua | 364 ++++----------------------------------------------------
 2 files changed, 383 insertions(+), 338 deletions(-)
 create mode 100644 client/game.lua

(limited to 'client')

diff --git a/client/game.lua b/client/game.lua
new file mode 100644
index 0000000..3445bd7
--- /dev/null
+++ b/client/game.lua
@@ -0,0 +1,357 @@
+local enet = require"enet"
+local json = require"common.dkjson"
+local utf8 = require"utf8"
+
+
+local SERVER_HOSTNAME = "ubq323.website"
+if os.getenv"HEXEMU_LOCAL" then SERVER_HOSTNAME = "localhost" end
+
+local PLAYER_SIZE = require"common.constants".PLAYER_SIZE
+local local_player = nil
+
+local drawing = require"drawing"
+local coords = require"common.coords"
+local Pos = coords.Pos
+local camera = require"camera".Camera:make()
+local ChunkC = require"chunk".ChunkC
+local util = require"util"
+local Map = require"common.map".Map
+local movement = require"movement"
+local msgbox = require"msgbox"
+local drawing2 = require"drawing2"
+
+-- local pprint=require"common.pprint"
+-- pprint.setup{show_all=true}
+
+local SCENE = {}
+
+local help_text = [[
+controls:
+wasd: move
+shift: sprint
+
+left mouse: place
+right mouse: destroy
+mousewheel: zoom in/out
+
+F1: show/hide this help
+F3: show/hide debug
+
+enter: toggle chat]]
+
+
+
+
+math.randomseed(os.time())
+
+local map = Map:make()
+
+local host,peer
+
+
+local selected_tile = 9
+
+-- normal: regular gameplay
+-- chat: chat box is open
+-- more modes may come
+local ui_mode = "normal"
+
+_G.debugmode = false
+
+local show_controls = false
+
+local this_chatmsg = ""
+local chatmsg_text = love.graphics.newText(love.graphics.getFont())
+
+
+function SCENE.keypressed(key,scancode,isrepeat)
+	if ui_mode == "normal" then
+		if key == "f3" then _G.debugmode = not _G.debugmode end
+		if key == "f1" then show_controls = not show_controls end
+		for i = 1,9 do if key == tostring(i) then selected_tile = i end end
+		if key == "return" then
+			ui_mode = "chat"
+			this_chatmsg = ""
+		end
+	elseif ui_mode == "chat" then
+		if key == "return" then
+			ui_mode = "normal"
+			if this_chatmsg:sub(1,3) == "/tp" then
+				local x,y = this_chatmsg:match("/tp (%S+) (%S+)")
+				if x then
+					x,y = tonumber(x),tonumber(y)
+					local_player.pos.x=x
+					local_player.pos.y=y
+					local_player.pos_dirty=true
+				end
+			else
+				peer:send(json.encode{t="chat",msg=this_chatmsg})
+			end
+			-- msgbox.add("[me] "..this_chatmsg)
+		elseif key == "escape" then
+			ui_mode = "normal"
+		elseif key == "backspace" then
+			local boffs = utf8.offset(this_chatmsg,-1)
+			if boffs then
+				this_chatmsg = this_chatmsg:sub(1,boffs-1)
+			end
+			
+		end
+	end
+end
+
+function SCENE.textinput(text)
+	if ui_mode == "chat" then
+		this_chatmsg = this_chatmsg..text
+	end
+end
+
+local function draw_player(pl,islocal)
+	love.graphics.setColor(pl.color)
+	love.graphics.circle("fill",pl.pos.x,pl.pos.y, PLAYER_SIZE)
+
+	if islocal then
+		love.graphics.setLineWidth(0.01)
+		love.graphics.setColor(0.5,0,0)
+		love.graphics.circle("line",pl.pos.x,pl.pos.y,PLAYER_SIZE)
+	end
+end
+
+local remote_players = {}
+
+local function update_local_player(pl,dt)
+	local SPEED = 8*math.sqrt(3) -- 8 hexagonheights per second
+
+	if love.keyboard.isDown("lshift") then SPEED = SPEED*2 end
+	local function kd(code)
+		if love.keyboard.isScancodeDown(code) then return 1 else return 0 end
+	end
+	local dx = kd"d"-kd"a"
+	local dy = kd"s"-kd"w"
+
+	if dx == 0 and dy == 0 then
+		return
+	end
+
+	if dx ~= 0 and dy ~= 0 then
+		-- 60degrees direction, to follow hex grid
+		-- instead of 45degrees diagonal
+		dx = dx * 0.5
+		dy = dy * (math.sqrt(3)/2)
+	end
+
+	local try_pos = Pos:make(pl.pos.x + SPEED*dt*dx, pl.pos.y + SPEED*dt*dy)
+	pl.pos = movement.collide_with_terrain(pl.pos,try_pos,map)
+	-- pl.pos = try_pos
+	pl.pos_dirty = true
+end
+
+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},1)
+		pl.pos_dirty = false
+	end
+end
+
+local function send_settile(hpos,tile)
+	peer:send(json.encode{t="settile",q=hpos.q,r=hpos.r,tile=tile})
+end
+
+function SCENE.wheelmoved(dx,dy)
+	camera.zoom = camera.zoom * (1.15 ^ dy)
+	camera.zoom = math.max(2.5,math.min(50,camera.zoom))
+end
+
+function SCENE.update(dt)
+	msgbox.update(dt)
+	if ui_mode == "normal" then
+		
+		-- movement and zoom in/out (keyboard input)
+		if local_player then
+			update_local_player(local_player,dt)
+			sync_local_player(local_player)
+		end
+
+		-- mouse input
+		local msx,msy = love.mouse.getPosition()
+		local mh = camera:screen_to_world(Pos:make(msx,msy)):to_hex():round()
+		if map:at(mh) == 0 and love.mouse.isDown(1) then
+			map:set_at(mh,selected_tile)
+			send_settile(mh,selected_tile)
+		elseif map:at(mh) ~= 0 and love.mouse.isDown(2) then
+			map:set_at(mh,0)
+			send_settile(mh,0)
+		end
+	end
+
+	-- load and unload chunks
+	if local_player then
+		local player_cp = local_player.pos:to_hex():containing_chunk()
+		-- load chunks near to player (within 3x3 square)
+		for _,cp in ipairs(player_cp:neighborhood()) do
+			if map:chunk(cp) == nil then
+				map:mark_chunk_loading(cp)
+				peer:send(json.encode{t="reqchunk",u=cp.u,v=cp.v})
+			end
+		end
+
+		-- unload chunks not near player
+		-- todo maybe: instead of immediately unloading chunks when we move away,
+		-- have some kind of 'last near' time, so that if player is moving back and forth,
+		-- we don't repeatedly unload and reload a given chunk
+		local to_remove = {}
+		for cp in map:iter_chunks() do
+			local d = player_cp:orth_dist(cp)
+			if d > 1 then
+				map:remove_chunk(cp)
+			end
+		end
+	
+	end
+	
+
+	-- handle network packets
+	repeat
+		local ev = host:service()
+		if ev and ev.type == "receive" then
+			-- print(ev.data)
+			local j = json.decode(ev.data)
+			local op = j.t
+			-- if op ~= "chunk" then print(ev.channel,ev.data) end
+			if op == "join" then
+				local pl = j.pl
+				remote_players[pl.id] = {pos=coords.Pos:make(pl.x,pl.y),color=pl.color,id=pl.id}
+				msgbox.add(pl.id.." joined")
+			elseif op == "leave" then
+				local id = j.id
+				remote_players[id]=nil
+				msgbox.add(id.." left")
+			elseif op == "move" then
+				local id,x,y = j.id,j.x,j.y
+				assert(remote_players[id],"wheeze "..id)
+				remote_players[id].pos.x = x
+				remote_players[id].pos.y = y
+			elseif op == "you" then
+				local pl = j.pl
+				local_player = {pos=coords.Pos:make(pl.x,pl.y),color=pl.color,id=pl.id}
+			elseif op == "chunk" then
+				local ch = ChunkC:from_packet_data(j)
+				map:add_chunk(ch)
+			elseif op == "settile" then
+				local h = coords.Hex:make(j.q,j.r)
+				map:set_at(h,j.tile)
+			elseif op == "chat" then
+				local msg,from = j.msg,j.from
+				msgbox.add("["..tostring(from).."] "..msg)
+			end
+		end
+	until not ev
+end
+
+function SCENE.draw()
+	love.graphics.clear(1,1,1)
+	love.graphics.origin()
+	if local_player then
+		camera.pos = local_player.pos
+	end
+	camera:apply_trans()
+
+	-- drawing.draw_map(camera,map)
+	drawing2.draw_map(camera,map)
+
+
+	if local_player then
+		draw_player(local_player,true)
+	end
+	for _,pl in pairs(remote_players) do
+		draw_player(pl)
+	end
+
+	love.graphics.setColor(1,0,0)
+	love.graphics.rectangle("fill",0,0,1,1)
+
+	local sm = Pos:make(love.mouse.getPosition())
+	local wm = camera:screen_to_world(sm)
+	local hm = wm:to_hex()
+
+	-- draw reticle
+	local hmr = hm:round()
+	local mouse_tile = map:at(hmr)
+	if mouse_tile then
+		local col = {0.5,0.5,0.5}
+		if mouse_tile == 0 then col = {0,0,0} end
+		local c = hmr:to_pos()
+		local verts = {}
+		for i=0,5 do
+			local angle = math.pi*2*(i+0.5)/6
+			table.insert(verts, c.x+math.cos(angle))
+			table.insert(verts, c.y+math.sin(angle))
+		end
+		love.graphics.setColor(col)
+		love.graphics.setLineWidth(0.05)
+		love.graphics.polygon("line",verts)
+	end
+
+	
+
+	love.graphics.origin()
+	util.print_good(tostring(selected_tile), "center",10)
+	if _G.debugmode and local_player then
+		util.print_good(table.concat({
+			 "ms "..tostring(sm),
+			 "mw "..tostring(wm),
+			 "mh "..tostring(hm).." "..tostring(hm:round()),
+			 "",
+			 "pw "..tostring(local_player.pos),
+			 "ph "..tostring(local_player.pos:to_hex()).." "..tostring(local_player.pos:to_hex():round()),
+			 "",
+			 "voob "..tostring(camera.zoom),
+			 "",
+			 "fps "..tostring(love.timer.getFPS()),
+			 "ping "..tostring(peer:round_trip_time()),
+
+		 },"\n"),10,10)
+	 end
+	 if show_controls then
+		 util.print_good(help_text,"center","center")
+	 end
+
+	 msgbox.draw()
+	 
+	 if ui_mode ~= "normal" then
+		 util.print_good(ui_mode, -20,10)
+	 end
+
+	 if ui_mode == "chat" then
+		 local W,H = love.graphics.getDimensions()
+		 chatmsg_text:set("- "..this_chatmsg)
+		 local tw,th = chatmsg_text:getDimensions()
+		 local y = H-th-30
+		 love.graphics.setColor(0,0,0,0.8)
+		 love.graphics.rectangle("fill",0,y,W,th)
+		 love.graphics.setColor(1,1,1)
+		 love.graphics.draw(chatmsg_text,0,y)
+		 love.graphics.setColor(0.8,0.8,0.8)
+		 love.graphics.line(tw,y,tw,y+th)
+	 end
+
+
+end
+
+function SCENE.load()
+	love.keyboard.setKeyRepeat(true)
+	-- require"profile".start(10,io.open("./trace","w"))
+	host = enet.host_create()
+	peer = host:connect(SERVER_HOSTNAME..":8473",2)
+	msgbox.add("connected to "..SERVER_HOSTNAME..":8473")
+	msgbox.add("press F1 for controls help")
+end
+
+function SCENE.quit()
+	-- require"profile".stop()
+	peer:disconnect()
+	host:flush()
+end
+
+return SCENE
diff --git a/client/main.lua b/client/main.lua
index c2d5a76..f52801c 100644
--- a/client/main.lua
+++ b/client/main.lua
@@ -1,354 +1,42 @@
-local enet = require"enet"
-local json = require"common.dkjson"
-local utf8 = require"utf8"
+local gamescene = require"game"
 
-
-local SERVER_HOSTNAME = "ubq323.website"
-if os.getenv"HEXEMU_LOCAL" then SERVER_HOSTNAME = "localhost" end
-
-local PLAYER_SIZE = require"common.constants".PLAYER_SIZE
-local local_player = nil
-
-local drawing = require"drawing"
-local coords = require"common.coords"
-local Pos = coords.Pos
-local camera = require"camera".Camera:make()
-local ChunkC = require"chunk".ChunkC
-local util = require"util"
-local Map = require"common.map".Map
-local movement = require"movement"
-local msgbox = require"msgbox"
-local drawing2 = require"drawing2"
-
--- local pprint=require"common.pprint"
--- pprint.setup{show_all=true}
-
-
-local help_text = [[
-controls:
-wasd: move
-shift: sprint
-
-left mouse: place
-right mouse: destroy
-mousewheel: zoom in/out
-
-F1: show/hide this help
-F3: show/hide debug
-
-enter: toggle chat]]
-
-
-
-
-math.randomseed(os.time())
-
-local map = Map:make()
-
-local host,peer
-
-
-local selected_tile = 9
-
--- normal: regular gameplay
--- chat: chat box is open
--- more modes may come
-local ui_mode = "normal"
-
-_G.debugmode = false
-
-local show_controls = false
-
-local this_chatmsg = ""
-local chatmsg_text = love.graphics.newText(love.graphics.getFont())
-
-
-function love.keypressed(key,scancode,isrepeat)
-	if ui_mode == "normal" then
-		if key == "f3" then _G.debugmode = not _G.debugmode end
-		if key == "f1" then show_controls = not show_controls end
-		for i = 1,9 do if key == tostring(i) then selected_tile = i end end
-		if key == "return" then
-			ui_mode = "chat"
-			this_chatmsg = ""
-		end
-	elseif ui_mode == "chat" then
-		if key == "return" then
-			ui_mode = "normal"
-			if this_chatmsg:sub(1,3) == "/tp" then
-				local x,y = this_chatmsg:match("/tp (%S+) (%S+)")
-				if x then
-					x,y = tonumber(x),tonumber(y)
-					local_player.pos.x=x
-					local_player.pos.y=y
-					local_player.pos_dirty=true
-				end
-			else
-				peer:send(json.encode{t="chat",msg=this_chatmsg})
-			end
-			-- msgbox.add("[me] "..this_chatmsg)
-		elseif key == "escape" then
-			ui_mode = "normal"
-		elseif key == "backspace" then
-			local boffs = utf8.offset(this_chatmsg,-1)
-			if boffs then
-				this_chatmsg = this_chatmsg:sub(1,boffs-1)
-			end
-			
-		end
-	end
-end
-
-function love.textinput(text)
-	if ui_mode == "chat" then
-		this_chatmsg = this_chatmsg..text
-	end
-end
-
-local function draw_player(pl,islocal)
-	love.graphics.setColor(pl.color)
-	love.graphics.circle("fill",pl.pos.x,pl.pos.y, PLAYER_SIZE)
-
-	if islocal then
-		love.graphics.setLineWidth(0.01)
-		love.graphics.setColor(0.5,0,0)
-		love.graphics.circle("line",pl.pos.x,pl.pos.y,PLAYER_SIZE)
-	end
-end
-
-local remote_players = {}
-
-local function update_local_player(pl,dt)
-	local SPEED = 8*math.sqrt(3) -- 8 hexagonheights per second
-
-	if love.keyboard.isDown("lshift") then SPEED = SPEED*2 end
-	local function kd(code)
-		if love.keyboard.isScancodeDown(code) then return 1 else return 0 end
-	end
-	local dx = kd"d"-kd"a"
-	local dy = kd"s"-kd"w"
-
-	if dx == 0 and dy == 0 then
-		return
-	end
-
-	if dx ~= 0 and dy ~= 0 then
-		-- 60degrees direction, to follow hex grid
-		-- instead of 45degrees diagonal
-		dx = dx * 0.5
-		dy = dy * (math.sqrt(3)/2)
-	end
-
-	local try_pos = Pos:make(pl.pos.x + SPEED*dt*dx, pl.pos.y + SPEED*dt*dy)
-	pl.pos = movement.collide_with_terrain(pl.pos,try_pos,map)
-	-- pl.pos = try_pos
-	pl.pos_dirty = true
-end
-
-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},1)
-		pl.pos_dirty = false
-	end
+local current_scene
+local function switch_scene(newscene)
+	if current_scene.quit then current_scene.quit() end
+	current_scene = newscene
+	if newscene.load then newscene.load() end
 end
 
-local function send_settile(hpos,tile)
-	peer:send(json.encode{t="settile",q=hpos.q,r=hpos.r,tile=tile})
-end
-
-function love.wheelmoved(dx,dy)
-	camera.zoom = camera.zoom * (1.15 ^ dy)
-	camera.zoom = math.max(2.5,math.min(50,camera.zoom))
-end
 
-function love.update(dt)
-	msgbox.update(dt)
-	if ui_mode == "normal" then
-		
-		-- movement and zoom in/out (keyboard input)
-		if local_player then
-			update_local_player(local_player,dt)
-			sync_local_player(local_player)
-		end
-
-		-- mouse input
-		local msx,msy = love.mouse.getPosition()
-		local mh = camera:screen_to_world(Pos:make(msx,msy)):to_hex():round()
-		if map:at(mh) == 0 and love.mouse.isDown(1) then
-			map:set_at(mh,selected_tile)
-			send_settile(mh,selected_tile)
-		elseif map:at(mh) ~= 0 and love.mouse.isDown(2) then
-			map:set_at(mh,0)
-			send_settile(mh,0)
-		end
-	end
+local big_font = love.graphics.newFont(72)
+local normal_font = love.graphics.getFont()
 
-	-- load and unload chunks
-	if local_player then
-		local player_cp = local_player.pos:to_hex():containing_chunk()
-		-- load chunks near to player (within 3x3 square)
-		for _,cp in ipairs(player_cp:neighborhood()) do
-			if map:chunk(cp) == nil then
-				map:mark_chunk_loading(cp)
-				peer:send(json.encode{t="reqchunk",u=cp.u,v=cp.v})
-			end
-		end
-
-		-- unload chunks not near player
-		-- todo maybe: instead of immediately unloading chunks when we move away,
-		-- have some kind of 'last near' time, so that if player is moving back and forth,
-		-- we don't repeatedly unload and reload a given chunk
-		local to_remove = {}
-		for cp in map:iter_chunks() do
-			local d = player_cp:orth_dist(cp)
-			if d > 1 then
-				map:remove_chunk(cp)
-			end
-		end
-	
-	end
-	
-
-	-- handle network packets
-	repeat
-		local ev = host:service()
-		if ev and ev.type == "receive" then
-			-- print(ev.data)
-			local j = json.decode(ev.data)
-			local op = j.t
-			-- if op ~= "chunk" then print(ev.channel,ev.data) end
-			if op == "join" then
-				local pl = j.pl
-				remote_players[pl.id] = {pos=coords.Pos:make(pl.x,pl.y),color=pl.color,id=pl.id}
-				msgbox.add(pl.id.." joined")
-			elseif op == "leave" then
-				local id = j.id
-				remote_players[id]=nil
-				msgbox.add(id.." left")
-			elseif op == "move" then
-				local id,x,y = j.id,j.x,j.y
-				assert(remote_players[id],"wheeze "..id)
-				remote_players[id].pos.x = x
-				remote_players[id].pos.y = y
-			elseif op == "you" then
-				local pl = j.pl
-				local_player = {pos=coords.Pos:make(pl.x,pl.y),color=pl.color,id=pl.id}
-			elseif op == "chunk" then
-				local ch = ChunkC:from_packet_data(j)
-				map:add_chunk(ch)
-			elseif op == "settile" then
-				local h = coords.Hex:make(j.q,j.r)
-				map:set_at(h,j.tile)
-			elseif op == "chat" then
-				local msg,from = j.msg,j.from
-				msgbox.add("["..tostring(from).."] "..msg)
-			end
-		end
-	until not ev
-end
-
-function love.draw()
+local titlescene = {}
+function titlescene.draw()
 	love.graphics.clear(1,1,1)
 	love.graphics.origin()
-	if local_player then
-		camera.pos = local_player.pos
-	end
-	camera:apply_trans()
-
-	-- drawing.draw_map(camera,map)
-	drawing2.draw_map(camera,map)
+	love.graphics.setColor(0,0,0)
+	love.graphics.setFont(big_font)
+	love.graphics.print("hexagon emulator",30,30)
 
-
-	if local_player then
-		draw_player(local_player,true)
-	end
-	for _,pl in pairs(remote_players) do
-		draw_player(pl)
+	love.graphics.setFont(normal_font)
+	love.graphics.print("press <enter> to start",40,120)
+end
+local evilscene
+function titlescene.keypressed(k,s,r)
+	if k=="return" then
+		switch_scene(gamescene)
 	end
+end
 
-	love.graphics.setColor(1,0,0)
-	love.graphics.rectangle("fill",0,0,1,1)
 
-	local sm = Pos:make(love.mouse.getPosition())
-	local wm = camera:screen_to_world(sm)
-	local hm = wm:to_hex()
+current_scene=titlescene
 
-	-- draw reticle
-	local hmr = hm:round()
-	local mouse_tile = map:at(hmr)
-	if mouse_tile then
-		local col = {0.5,0.5,0.5}
-		if mouse_tile == 0 then col = {0,0,0} end
-		local c = hmr:to_pos()
-		local verts = {}
-		for i=0,5 do
-			local angle = math.pi*2*(i+0.5)/6
-			table.insert(verts, c.x+math.cos(angle))
-			table.insert(verts, c.y+math.sin(angle))
-		end
-		love.graphics.setColor(col)
-		love.graphics.setLineWidth(0.05)
-		love.graphics.polygon("line",verts)
+for _,f in ipairs{"update","draw","keypressed","textinput","wheelmoved"} do
+	love[f] = function(...)
+		local x = current_scene[f]
+		if x then return x(...) end
 	end
-
-	
-
-	love.graphics.origin()
-	util.print_good(tostring(selected_tile), "center",10)
-	if _G.debugmode and local_player then
-		util.print_good(table.concat({
-			 "ms "..tostring(sm),
-			 "mw "..tostring(wm),
-			 "mh "..tostring(hm).." "..tostring(hm:round()),
-			 "",
-			 "pw "..tostring(local_player.pos),
-			 "ph "..tostring(local_player.pos:to_hex()).." "..tostring(local_player.pos:to_hex():round()),
-			 "",
-			 "voob "..tostring(camera.zoom),
-			 "",
-			 "fps "..tostring(love.timer.getFPS()),
-			 "ping "..tostring(peer:round_trip_time()),
-
-		 },"\n"),10,10)
-	 end
-	 if show_controls then
-		 util.print_good(help_text,"center","center")
-	 end
-
-	 msgbox.draw()
-	 
-	 if ui_mode ~= "normal" then
-		 util.print_good(ui_mode, -20,10)
-	 end
-
-	 if ui_mode == "chat" then
-		 local W,H = love.graphics.getDimensions()
-		 chatmsg_text:set("- "..this_chatmsg)
-		 local tw,th = chatmsg_text:getDimensions()
-		 local y = H-th-30
-		 love.graphics.setColor(0,0,0,0.8)
-		 love.graphics.rectangle("fill",0,y,W,th)
-		 love.graphics.setColor(1,1,1)
-		 love.graphics.draw(chatmsg_text,0,y)
-		 love.graphics.setColor(0.8,0.8,0.8)
-		 love.graphics.line(tw,y,tw,y+th)
-	 end
-
-
 end
 
-function love.load()
-	love.keyboard.setKeyRepeat(true)
-	-- require"profile".start(10,io.open("./trace","w"))
-	host = enet.host_create()
-	peer = host:connect(SERVER_HOSTNAME..":8473",2)
-	msgbox.add("connected to "..SERVER_HOSTNAME..":8473")
-	msgbox.add("press F1 for controls help")
-end
 
-function love.quit()
-	-- require"profile".stop()
-	peer:disconnect()
-	host:flush()
-end
-- 
cgit v1.2.3