From dd12ceb233aa44101f819700d5467d8ba4d2fb4e Mon Sep 17 00:00:00 2001 From: ubq323 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 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