summaryrefslogtreecommitdiff
path: root/client/game.lua
diff options
context:
space:
mode:
Diffstat (limited to 'client/game.lua')
-rw-r--r--client/game.lua357
1 files changed, 357 insertions, 0 deletions
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