local enet = require"enet" local json = require"common.dkjson" local SERVER_HOSTNAME = "ubq323.website" if os.getenv"HEXEMU_LOCAL" then SERVER_HOSTNAME = "localhost" end local PLAYER_SIZE = 0.7 local local_player = nil local drawing = require"drawing" local coords = require"common.coords" 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 movement = require"movement" -- local pprint=require"common.pprint" -- pprint.setup{show_all=true} local help_text = [[ controls: wasd: move left mouse: place q,e: zoom in/out right mouse: destroy shift: sprint F3: toggle debug F1: show/hide this help]] math.randomseed(os.time()) local map = Map:make() local host,peer _G.debugmode = false local show_controls = true function love.keypressed(key,scancode,isrepeat) if scancode == "f3" then _G.debugmode = not _G.debugmode end if scancode == "f1" then show_controls = not show_controls end end local function draw_player(pl,islocal) local hplsz = PLAYER_SIZE/2 love.graphics.setColor(pl.color) love.graphics.rectangle("fill",pl.pos.x-hplsz,pl.pos.y-hplsz,PLAYER_SIZE,PLAYER_SIZE) -- love.graphics.print(tostring(pl.id),pl.pos.x,pl.pos.y) if islocal then love.graphics.setLineWidth(0.01) love.graphics.setColor(0.5,0,0) love.graphics.rectangle("line",pl.pos.x-hplsz,pl.pos.y-hplsz,PLAYER_SIZE,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.isScancodeDown("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_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}) 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 love.update(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.5,math.min(50,camera.zoom)) sync_local_player(local_player) end local mh = camera:screen_to_world(Pos:make(love.mouse.getPosition())):to_hex():round() if map:at(mh) == 0 and love.mouse.isDown(1) then map:set_at(mh,9) send_settile(mh,9) elseif map:at(mh) ~= 0 and love.mouse.isDown(2) then map:set_at(mh,0) send_settile(mh,0) end 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 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 == "join" then local pl = j.pl remote_players[pl.id] = {pos=coords.Pos:make(pl.x,pl.y),color=pl.color,id=pl.id} elseif op == "leave" then local id = j.id remote_players[id]=nil 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 = Chunk: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) end end until not ev end function love.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) 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() love.graphics.origin() if _G.debugmode and local_player then util.print_good({ "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()), },10,10) end if show_controls then util.print_good(help_text,300,200) end end function love.load() -- require"profile".start(10,io.open("./trace","w")) host = enet.host_create() peer = host:connect(SERVER_HOSTNAME..":8473") end function love.quit() -- require"profile".stop() peer:disconnect() host:flush() end