diff --git a/client.lua b/client.lua index f03b5c6..eb74bb1 100644 --- a/client.lua +++ b/client.lua @@ -29,10 +29,13 @@ function client.connect(ip, port) client.currentState.projectiles[v.id] = v end for _, v in pairs(data.entities) do - local ent = entities.client.defs[v.type]:new(v):spawn() + entities.client.defs[v.type]:new(v):spawn() end for _, v in pairs(data.lootBags) do - client.currentState.lootBags[v.id] = v + lootBag.client:new(v):spawn() + end + for _, v in pairs(data.portals) do + client.currentState.portals[v.id] = v end end, remove = function(self, data) @@ -42,10 +45,13 @@ function client.connect(ip, port) for _, id in pairs(data.entities) do local ent = client.currentState.entities[id] if ent then ent:destroy() end - client.currentState.entities[id] = nil end for _, id in pairs(data.lootBags) do - client.currentState.lootBags[id] = nil + local bag = clientRealm.lootBags[id] + if bag then bag:destroy() end + end + for _, id in pairs(data.portals) do + client.currentState.portals[id] = nil end end, stateUpdate = function(self, data) @@ -65,10 +71,22 @@ function client.connect(ip, port) items.client.requested[data.id] = nil end, bagUpdate = function(self, data) - client.currentState.lootBags[data.id] = data + local bag = clientRealm.lootBags[data.id] + if bag then + for k, v in pairs(data) do + bag[k] = v + end + end end, setWorldChunk = function(self, data) - world.client.setChunk(data.x, data.y, data.chunk) + clientRealm.world:setChunk(data.x, data.y, data.chunk) + end, + teleportPlayer = function(self, data) + playerController.player.body:setPosition(data.x, data.y) + end, + healPlayer = function(self, data) + local p = playerController.player + p.hp = math.min(p.hp + data.hp, p.hpMax) end } for k, v in pairs(bitserRPCs) do @@ -98,7 +116,7 @@ function client.connect(ip, port) -- cleanup previous connection if client.currentState then entities.client.reset() - world.client.reset() + clientRealm:destroy() slimeBalls.reset() items.client.reset() collectgarbage() @@ -107,12 +125,12 @@ function client.connect(ip, port) menu.writeDefaults() - physics.client.load() + clientRealm:load() playerController.load() end function client.newState() - return {entities={}, projectiles={}, lootBags={}} + return {entities={}, projectiles={}, lootBags={}, portals={}} end function client.startGame(data) @@ -200,9 +218,7 @@ function client.update(dt) if not (server.running and server.paused) then gameTime = gameTime + dt hud.update(dt) - lootBags.client.update(dt) - physics.client.update(dt) - world.client.update(dt) + clientRealm:update(dt) playerController.update(dt) entities.client.update(dt) slimeBalls.update(dt) @@ -215,7 +231,7 @@ function client.sendMessage(msg) end end -for _, v in pairs{'spawnProjectile', 'moveItem', 'dropItem'} do +for _, v in pairs{'spawnProjectile', 'moveItem', 'dropItem', 'useItem', 'usePortal'} do client[v] = function(data) client.nutClient:sendRPC(v, bitser.dumps(data)) end diff --git a/entityDefs/_base.lua b/entityDefs/_base.lua index 28b39a1..a904eb1 100644 --- a/entityDefs/_base.lua +++ b/entityDefs/_base.lua @@ -108,6 +108,7 @@ end function base.client:destroy() self.destroyed = true + client.currentState.entities[self.id] = nil end diff --git a/entityDefs/player.lua b/entityDefs/player.lua index 51a5a57..96f758a 100644 --- a/entityDefs/player.lua +++ b/entityDefs/player.lua @@ -58,7 +58,7 @@ function player.server:new(o) end function player.server:spawn() - self.body = love.physics.newBody(physics.server.world, self.x, self.y, 'dynamic') + self.body = love.physics.newBody(serverRealm.physics.world, self.x, self.y, 'dynamic') self.shapes = { love.physics.newCircleShape(6) } @@ -142,8 +142,8 @@ function player.server:update(dt) dy = dy + (self.inputState.keyboard.w and -1 or 0) dy = dy + (self.inputState.keyboard.s and 1 or 0) local spd = self.spd*(self.inputState.keyboard.lshift and 2.5 or 1) - if world.server.getTile(self.x, self.y) == 5 then spd = spd * 1.5 end -- platform - if world.server.getTile(self.x, self.y) == 4 then spd = spd / 2 end -- water + if serverRealm.world:getTile(self.x, self.y) == 5 then spd = spd * 1.5 end -- platform + if serverRealm.world:getTile(self.x, self.y) == 4 then spd = spd / 2 end -- water local attackItem = items.server.getItem(self.inventory.items[2]) if not (dx == 0 and dy == 0) and not (self.swinging or self.automaticSwing @@ -205,7 +205,7 @@ function player.client:new(o) end function player.client:spawn() - self.body = love.physics.newBody(physics.client.world, self.x, self.y, 'dynamic') + self.body = love.physics.newBody(clientRealm.physics.world, self.x, self.y, 'dynamic') self.shapes = { love.physics.newCircleShape(6) } @@ -309,8 +309,8 @@ function player.client:update(dt) dy = dy + (self.inputState.keyboard.w and -1 or 0) dy = dy + (self.inputState.keyboard.s and 1 or 0) local spd = self.spd*(self.inputState.keyboard.lshift and 2.5 or 1) - if world.client.getTile(self.x, self.y) == 5 then spd = spd * 1.5 end -- platform - if world.client.getTile(self.x, self.y) == 4 then spd = spd / 2 end -- water + if clientRealm.world:getTile(self.x, self.y) == 5 then spd = spd * 1.5 end -- platform + if clientRealm.world:getTile(self.x, self.y) == 4 then spd = spd / 2 end -- water local attackItem = items.client.getItem(self.inventory.items[2]) if not (dx == 0 and dy == 0) and not (self.swinging or self.automaticSwing @@ -370,7 +370,7 @@ function player.client:draw() love.graphics.push() -- offset if on platform - if world.client.getTile(self.x, self.y) == 5 then + if clientRealm.world:getTile(self.x, self.y) == 5 then love.graphics.translate(0, -2) end @@ -391,7 +391,7 @@ function player.client:draw() love.graphics.clear() -- offset/clip feet if in water - if world.client.getTile(self.x, self.y) == 4 then + if clientRealm.world:getTile(self.x, self.y) == 4 then love.graphics.translate(0, 4) love.graphics.stencil(function() love.graphics.rectangle('fill', self.x - 50, self.y - 4, 100, 100) diff --git a/entityDefs/slime.lua b/entityDefs/slime.lua index 4eea1f1..3374eba 100644 --- a/entityDefs/slime.lua +++ b/entityDefs/slime.lua @@ -33,7 +33,7 @@ function slime.server:new(o) end function slime.server:spawn() - self.body = love.physics.newBody(physics.server.world, self.x, self.y, 'dynamic') + self.body = love.physics.newBody(serverRealm.physics.world, self.x, self.y, 'dynamic') self.polys = { {0.36, 0.64, 0.08, 0.36, 0.08, 0.2, 0.2, 0.08, 0.8, 0.08, 0.92, 0.2, 0.92, 0.36, 0.64, 0.64} } @@ -85,12 +85,12 @@ function slime.server:damage(d, clientId) if self.hp <= 0 and not self.destroyed then server.addXP(clientId, math.random(3, 5)) for _=1, math.random(1, 2) do - local x = self.x + (math.random()*2-1)*64 - local y = self.y + (math.random()*2-1)*64 + local x = self.x + lume.random(-64, 64) + local y = self.y + lume.random(-64, 64) self:new{x=x, y=y}:spawn() end local bagItems = {} - local choices = {none=50, sword=25, shield=25} + local choices = {none=50, sword=15, shield=15, apple=20} for _=1, 3 do choice = lume.weightedchoice(choices) if choice ~= 'none' then @@ -105,13 +105,15 @@ function slime.server:damage(d, clientId) local numItems = #bagItems if numItems ~= 0 then local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBags.server.spawn{ + lootBag.server:new{ + realm = serverRealm, x = self.x, y = self.y, items = bagItems, type = type, life = 30 - } + }:spawn() end + --if math.random() < 0.5 then portals.server.spawn{x=self.x, y=self.y, life=10} end self:destroy() end end @@ -145,7 +147,7 @@ end function slime.client:spawn() - self.body = love.physics.newBody(physics.client.world, self.x, self.y, 'dynamic') + self.body = love.physics.newBody(clientRealm.physics.world, self.x, self.y, 'dynamic') self.polys = { {0.36, 0.64, 0.08, 0.36, 0.08, 0.2, 0.2, 0.08, 0.8, 0.08, 0.92, 0.2, 0.92, 0.36, 0.64, 0.64} } diff --git a/gfx/items/apple.png b/gfx/items/apple.png new file mode 100644 index 0000000..3fba6c9 Binary files /dev/null and b/gfx/items/apple.png differ diff --git a/hud.lua b/hud.lua index a87e022..93deebe 100644 --- a/hud.lua +++ b/hud.lua @@ -167,7 +167,7 @@ function hud.update(dt) end end -function hud.mousepressed(x, y, btn) +function hud.mousepressed(x, y, btn, isTouch, presses) mx, my = window2game(x, y) mx, my = lume.round(mx), lume.round(my) @@ -194,17 +194,25 @@ function hud.mousepressed(x, y, btn) and pmy >= slot.y and pmy <= slot.y + slot.h and panel.open then uiMouseDown = true if bag.items[slotId] then + -- use item + if btn == 1 and (love.keyboard.isScancodeDown('lshift') or presses > 1) then + client.useItem{ + bagId = bag.id, + slotId = slotId + } + end + -- move items if btn == 1 then - local heldItem = lootBags.client.heldItem + local heldItem = playerController.heldItem heldItem.bagId = bag.id heldItem.slotId = slotId heldItem.offset.x = slot.x - pmx heldItem.offset.y = slot.y - pmy elseif btn == 2 then - local closestBag = lootBags.client.closest + local closestBag = playerController.closestBag if closestBag.id and closestBag.open then local bagTo = client.currentState.lootBags[closestBag.id] - for bagSlotId, _ in ipairs(lootBags.client.slots) do + for bagSlotId, _ in ipairs(lootBagSlots) do if bagTo.items[bagSlotId] == nil then client.moveItem{ from = { @@ -226,10 +234,10 @@ function hud.mousepressed(x, y, btn) end end -function hud.mousereleased(x, y, btn) +function hud.mousereleased(x, y, btn, isTouch, presses) local mx, my = window2game(x, y) mx, my = lume.round(mx), lume.round(my) - local heldItem = lootBags.client.heldItem + local heldItem = playerController.heldItem if heldItem.bagId then local bagFrom = client.currentState.lootBags[heldItem.bagId] if heldItem.bagId == 'inventory' then @@ -321,7 +329,7 @@ function hud.draw() love.graphics.rectangle('fill', 11, 18, 131, 25) love.graphics.setShader(_shader) - world.client.drawMinimap() + clientRealm.world:drawMinimap() -- panels/buttons love.graphics.setColor(1, 1, 1) @@ -393,12 +401,12 @@ function hud.draw() and pmy >= slot.y and pmy <= slot.y + slot.h and panel.open then if item then cursor.cursor = cursor.hand - lootBags.client.hoveredItem = item + playerController.hoveredItem = item end love.graphics.setColor(1, 1, 1, 0.4) love.graphics.rectangle('fill', slot.x, slot.y, slot.w, slot.h) end - local heldItem = lootBags.client.heldItem + local heldItem = playerController.heldItem if not (heldItem.bagId == 'inventory' and heldItem.slotId == slotId) then if item then love.graphics.setColor(1, 1, 1) @@ -409,7 +417,7 @@ function hud.draw() love.graphics.pop() -- item info - local item = lootBags.client.hoveredItem + local item = playerController.hoveredItem if item then love.graphics.setColor(1, 1, 1) love.graphics.setShader(shaders.panel) @@ -442,7 +450,7 @@ function hud.draw() end -- held item - local heldItem = lootBags.client.heldItem + local heldItem = playerController.heldItem if heldItem.bagId then local bag = client.currentState.lootBags[heldItem.bagId] if heldItem.bagId == 'inventory' then diff --git a/loadassets.lua b/loadassets.lua index f1e0080..e9a0dcf 100644 --- a/loadassets.lua +++ b/loadassets.lua @@ -70,7 +70,8 @@ gfx = { lootBag1 = love.graphics.newImage('gfx/items/loot1.png'), lootBagFuse = love.graphics.newImage('gfx/items/loot-fuse.png'), sword = love.graphics.newImage('gfx/items/sword.png'), - shield = love.graphics.newImage('gfx/items/shield.png') + shield = love.graphics.newImage('gfx/items/shield.png'), + apple = love.graphics.newImage('gfx/items/apple.png') }, slimeBall = love.graphics.newImage('gfx/slime_ball.png') } diff --git a/lootBag.lua b/lootBag.lua new file mode 100644 index 0000000..4cce337 --- /dev/null +++ b/lootBag.lua @@ -0,0 +1,89 @@ + +lootBag = { + server = {}, + client = {} +} + +local defaults = {server={}, client={}} +for _, sc in pairs{'server', 'client'} do + for k, v in pairs{x=0, y=0, type='lootBag'} do + defaults[sc][k] = function() return v end + end + defaults[sc].id = function() return lume.uuid() end + defaults[sc].items = function() return {} end +end +defaults.server.realm = function() return serverRealm end +defaults.client.realm = function() return clientRealm end + +lootBagSlots = {} +for j=1, 2 do + for i=1, 4 do + table.insert(lootBagSlots, { + x = 7 + (i-1)*18, + y = 22 + (j-1)*18, + w = 15, + h = 15 + }) + end +end + + + +function lootBag.server:new(o) + o = o or {} + for k, v in pairs(defaults.server) do + if o[k] == nil then o[k] = v() end + end + setmetatable(o, self) + self.__index = self + return o +end + +function lootBag.server:spawn() + self.spawnTime = gameTime + + self.realm.lootBags[self.id] = self + server.added.lootBags[self.id] = self:serialize() + return self +end + +function lootBag.server:serialize() + -- todo: realm id + return { + id = self.id, + x = self.x, y = self.y, + type = self.type, + items = self.items, + life = self.life, + spawnTime = self.spawnTime + } +end + +function lootBag.server:destroy() + self.realm.lootBags[self.id] = nil + server.currentState.lootBags[self.id] = nil + server.removed.lootBags[self.id] = self.id +end + + + +function lootBag.client:new(o) + o = o or {} + for k, v in pairs(defaults.client) do + if o[k] == nil then o[k] = v() end + end + setmetatable(o, self) + self.__index = self + return o +end + +function lootBag.client:spawn() + self.realm.lootBags[self.id] = self + client.currentState.lootBags[self.id] = self + return self +end + +function lootBag.client:destroy() + self.realm.lootBags[self.id] = nil + client.currentState.lootBags[self.id] = nil +end diff --git a/lootBags.lua b/lootBags.lua deleted file mode 100644 index 7fb64dd..0000000 --- a/lootBags.lua +++ /dev/null @@ -1,257 +0,0 @@ - -lootBags = { - server = { - container = {} - }, - client = { - openRange = 30, - closest = {id=nil, dist=nil, open=false}, - hoveredItem = nil, - heldItem = {bagId=nil, slotId=nil, offset={x=0, y=0}} - } -} - -function lootBags.server.spawn(data) - local defaults = { - x = 0, y = 0, - type = 'lootBag', - items = {} -- should be table of strings (serializable) - -- life defaults to nil (permanent) - } - for k, v in pairs(defaults) do - if data[k] == nil then data[k] = v end - end - - data.id = lume.uuid() - data.spawnTime = gameTime - - lootBags.server.container[data.id] = data - local state = { - id = data.id, - x = data.x, y = data.y, - type = data.type, - items = data.items, - life = data.life, - spawnTime = data.spawnTime - } - server.currentState.lootBags[data.id] = state - server.added.lootBags[data.id] = state -end - -function lootBags.server.destroy(id) - lootBags.server.container[id] = nil - server.currentState.lootBags[id] = nil - server.removed.lootBags[id] = id -end - -function lootBags.server.reset() - for k, v in pairs(lootBags.server.container) do - lootBags.server.destroy(k) - end -end - -function lootBags.server.update(dt) - for k, bag in pairs(lootBags.server.container) do - if bag.life and gameTime - bag.spawnTime > bag.life then - lootBags.server.destroy(k) - end - end -end - - - -lootBags.client.slots = {} -for j=1, 2 do - for i=1, 4 do - table.insert(lootBags.client.slots, { - x = 7 + (i-1)*18, - y = 22 + (j-1)*18, - w = 15, - h = 15 - }) - end -end - -function lootBags.client.update(dt) - local closestBag = lootBags.client.closest - local px, py = playerController.player.body:getPosition() - local lastId = closestBag.id - closestBag.id = nil - closestBag.dist = nil - for _, bag in pairs(client.currentState.lootBags) do - local dist = math.sqrt((bag.x - px)^2 + (bag.y - py)^2) - if not closestBag.dist or dist < closestBag.dist then - closestBag.id = bag.id - closestBag.dist = dist - end - end - -- todo: open bag should be able to not be closest - if not closestBag.id or closestBag.id ~= lastId or closestBag.dist > lootBags.client.openRange then - closestBag.open = false - end - local heldItem = lootBags.client.heldItem - if heldItem.bagId ~= 'inventory' and (not closestBag.id - or closestBag.id ~= heldItem.bagId or closestBag.dist > lootBags.client.openRange) then - heldItem.bagId = nil - heldItem.slotId = nil - end - lootBags.client.hoveredItem = nil -end - -function lootBags.client.mousepressed(x, y, btn) - local mx, my = window2game(x, y) - mx, my = lume.round(mx), lume.round(my) - wmx, wmy = camera:screen2world(mx, my) - local closestBag = lootBags.client.closest - if closestBag.id and closestBag.open then - local bag = client.currentState.lootBags[closestBag.id] - local img = gfx.ui.bag - local bmx = wmx - (lume.round(bag.x) - lume.round(img:getWidth()/2)) - local bmy = wmy - (lume.round(bag.y) - img:getHeight() - 20) - for slotId, slot in ipairs(lootBags.client.slots) do - if bmx >= slot.x and bmx <= slot.x + slot.w - and bmy >= slot.y and bmy <= slot.y + slot.h then - uiMouseDown = true - if bag.items[slotId] then - if btn == 1 then - local heldItem = lootBags.client.heldItem - heldItem.bagId = bag.id - heldItem.slotId = slotId - heldItem.offset.x = slot.x - bmx - heldItem.offset.y = slot.y - bmy - elseif btn == 2 then - local p = playerController.player - for invSlotId, _ in ipairs(hud.inventorySlots) do - if p.inventory.items[invSlotId] == nil then - client.moveItem{ - from = { - bagId = bag.id, - slotId = slotId - }, - to = { - bagId = p.inventory.id, - slotId = invSlotId - } - } - break - end - end - end - end - break - end - end - end -end - -function lootBags.client.mousereleased(x, y, btn) - local mx, my = window2game(x, y) - mx, my = lume.round(mx), lume.round(my) - wmx, wmy = camera:screen2world(mx, my) - local closestBag = lootBags.client.closest - local heldItem = lootBags.client.heldItem - if closestBag.id and closestBag.open and heldItem.bagId then - local bagFrom = client.currentState.lootBags[heldItem.bagId] - if heldItem.bagId == 'inventory' then - bagFrom = playerController.player.inventory - end - local bagTo = client.currentState.lootBags[closestBag.id] - local img = gfx.ui.bag - local bmx = wmx - (lume.round(bagTo.x) - lume.round(img:getWidth()/2)) - local bmy = wmy - (lume.round(bagTo.y) - img:getHeight() - 20) - for slotId, slot in ipairs(lootBags.client.slots) do - if bmx >= slot.x and bmx <= slot.x + slot.w - and bmy >= slot.y and bmy <= slot.y + slot.h then - client.moveItem{ - from = { - bagId = bagFrom.id, - slotId = heldItem.slotId - }, - to = { - bagId = bagTo.id, - slotId = slotId - } - } - -- move clientside before response (will be corrected/affirmed) - local temp = bagTo.items[slotId] - bagTo.items[slotId] = bagFrom.items[heldItem.slotId] - bagFrom.items[heldItem.slotId] = temp - break - end - end - end -end - -function lootBags.client.keypressed(k, scancode, isrepeat) - if scancode == 'e' and not isrepeat then - local closestBag = lootBags.client.closest - if closestBag.id and closestBag.dist < lootBags.client.openRange then - closestBag.open = not closestBag.open - end - end -end - -function lootBags.client.draw() - local mx, my = window2game(love.mouse.getPosition()) - mx, my = lume.round(mx), lume.round(my) - wmx, wmy = camera:screen2world(mx, my) - for _, bag in pairs(client.currentState.lootBags) do - scene.add{ - draw = function() - local tint = 1 - local outlineColor = {0, 0, 0, 1} - if bag.life and client.serverTime - bag.spawnTime > bag.life - 3 then - local t = (client.serverTime - bag.spawnTime - bag.life + 3)/3 - tint = math.cos(t*5*math.pi)/4 + 1/4 + (1-t)/2 - tint = tint/2 + 1/2 - outlineColor = {0.8, 0, 0, 1} - end - local closestBag = lootBags.client.closest - if bag.id == closestBag.id and closestBag.dist < lootBags.client.openRange then - outlineColor = {0.8, 0.8, 0.8, 1} - end - love.graphics.setColor(tint, tint, tint) - love.graphics.push() - love.graphics.translate(lume.round(bag.x), lume.round(bag.y)) - local img = gfx.items[bag.type] - local _shader = love.graphics.getShader() - love.graphics.setShader(shaders.outline) - shaders.outline:send('stepSize', {1/img:getWidth(), 1/img:getHeight()}) - shaders.outline:send('outlineColor', outlineColor) - love.graphics.draw(img, 0, 0, 0, 1, 1, lume.round(img:getWidth()/2), img:getHeight()) - love.graphics.setShader(_shader) - if bag.id == closestBag.id and closestBag.open then - local img = gfx.ui.bag - love.graphics.push() - love.graphics.translate(-lume.round(img:getWidth()/2), -img:getHeight() - 20) - love.graphics.setColor(1, 1, 1) - love.graphics.draw(img, 0, 0) - local bmx = wmx - (lume.round(bag.x) - lume.round(img:getWidth()/2)) - local bmy = wmy - (lume.round(bag.y) - img:getHeight() - 20) - for slotId, slot in ipairs(lootBags.client.slots) do - local item = items.client.getItem(bag.items[slotId]) - if bmx >= slot.x and bmx <= slot.x + slot.w - and bmy >= slot.y and bmy <= slot.y + slot.h then - if item then - cursor.cursor = cursor.hand - lootBags.client.hoveredItem = item - end - love.graphics.setColor(1, 1, 1, 0.4) - love.graphics.rectangle('fill', slot.x, slot.y, slot.w, slot.h) - end - local heldItem = lootBags.client.heldItem - if not (heldItem.bagId == bag.id and heldItem.slotId == slotId) then - if item then - love.graphics.setColor(1, 1, 1) - love.graphics.draw(gfx.items[item.imageId], slot.x, slot.y) - end - end - end - love.graphics.pop() - end - love.graphics.pop() - end, - y = bag.y - } - end -end diff --git a/main.lua b/main.lua index 83b6d1b..ca6eb86 100644 --- a/main.lua +++ b/main.lua @@ -25,7 +25,9 @@ require 'projectiles' require 'slimeBalls' require 'playerController' require 'items' -require 'lootBags' +require 'lootBag' +require 'portals' +require 'realm' require 'chat' function love.load() @@ -38,6 +40,8 @@ function love.load() drawDebug = false menu.load() hud.load() + serverRealm = realm.server:new() + clientRealm = realm.client:new() love.mouse.setVisible(false) end @@ -101,24 +105,23 @@ function love.update(dt) prof.pop('update') end -function love.mousepressed(x, y, btn, isTouch) - menu.mousepressed(x, y, btn) +function love.mousepressed(x, y, btn, isTouch, presses) + menu.mousepressed(x, y, btn, isTouch, presses) if gameState == 'playing' then - hud.mousepressed(x, y, btn) - lootBags.client.mousepressed(x, y, btn) - playerController.mousepressed(x, y, btn) + hud.mousepressed(x, y, btn, isTouch, presses) + playerController.mousepressed(x, y, btn, isTouch, presses) end end -function love.mousereleased(x, y, btn, isTouch) - menu.mousereleased(x, y, btn) +function love.mousereleased(x, y, btn, isTouch, presses) + menu.mousereleased(x, y, btn, isTouch, presses) if gameState == 'playing' then - hud.mousereleased(x, y, btn) - lootBags.client.mousereleased(x, y, btn) + hud.mousereleased(x, y, btn, isTouch, presses) + playerController.mousereleased(x, y, btn, isTouch, presses) end uiMouseDown = false - local heldItem = lootBags.client.heldItem + local heldItem = playerController.heldItem heldItem.bagId = nil heldItem.slotId = nil end @@ -149,7 +152,8 @@ function love.keypressed(k, scancode, isrepeat) menu.keypressed(k, scancode, isrepeat) elseif gameState == 'playing' then hud.keypressed(k, scancode, isrepeat) - lootBags.client.keypressed(k, scancode, isrepeat) + playerController.keypressed(k, scancode, isrepeat) + portals.client.keypressed(k, scancode, isrepeat) end if not isrepeat then if k == 'escape' and not chatPanelOpen then @@ -184,25 +188,22 @@ function love.draw() prof.push('draw playing') camera:set() - prof.push('draw world') - world.client.draw() - prof.pop('draw world') - prof.push('draw entities') + prof.push('draw scene') + scene.reset() + clientRealm:draw() + entities.client.draw() - prof.pop('draw entities') projectiles.client.draw() slimeBalls.draw() - lootBags.client.draw() + portals.client.draw() - prof.push('draw scene') scene.draw() - scene.reset() prof.pop('draw scene') prof.push('draw debug') if drawDebug then if server.running then - local serverBodies = physics.server.world:getBodies() + local serverBodies = serverRealm.physics.world:getBodies() love.graphics.setColor(1, 0, 0, 0.5) for _, v in pairs(serverBodies) do local x, y = v:getPosition() @@ -210,7 +211,7 @@ function love.draw() end end if client.connected then - local clientBodies = physics.client.world:getBodies() + local clientBodies = clientRealm.physics.world:getBodies() love.graphics.setColor(0, 1, 0, 0.5) for _, v in pairs(clientBodies) do local x, y = v:getPosition() diff --git a/menu.lua b/menu.lua index 96e3c4d..69fe126 100644 --- a/menu.lua +++ b/menu.lua @@ -85,7 +85,7 @@ function menu.load() resolution = 2, fullscreen = 1, vsync = true, - cursorLock = true + cursorLock = false } local menuDefaults diff --git a/physics.lua b/physics.lua index 8b55424..fc0f317 100644 --- a/physics.lua +++ b/physics.lua @@ -1,114 +1,119 @@ physics = { - server = { - postUpdateQueue = {} - }, - client = { - postUpdateQueue = {} - } + server = {}, + client = {} } love.physics.setMeter(16) -function physics.server.load() - if physics.server.world then physics.server.world:destroy() end - physics.server.world = love.physics.newWorld(0, 0, true) - physics.server.world:setCallbacks(physics.server.beginContact, - physics.server.endContact, physics.server.preSolve, physics.server.postSolve) +local defaults = {server={}, client={}} +for _, sc in pairs{'server', 'client'} do + defaults[sc].postUpdateQueue = function() return {} end end -function physics.server.update(dt) - physics.server.world:update(dt) - for i, v in pairs(physics.server.postUpdateQueue) do - v() - physics.server.postUpdateQueue[i] = nil - end -end --- can't do stuff like destroying fixtures in callbacks - queue for update -function physics.server.postUpdatePush(f) - table.insert(physics.server.postUpdateQueue, f) + +function physics.server:new(o) + o = o or {} + for k, v in pairs(defaults.server) do + if o[k] == nil then o[k] = v() end + end + setmetatable(o, self) + self.__index = self + return o end -function physics.server.beginContact(a, b, coll) - for _, v in pairs{{a, b}, {b, a}} do - local fixa = v[1] - local fixb = v[2] - local uda = fixa:getUserData() or {} - local udb = fixb:getUserData() or {} - if uda.type == 'playerSwing' and udb.enemy then - physics.server.postUpdatePush(function() - local swing = projectiles.server.container[uda.id] - local enemy = udb - if swing and enemy and not enemy.hitBy[swing.id] then - enemy.hitBy[swing.id] = true - enemy:damage(swing.damage, swing.playerId) - swing.pierce = swing.pierce - 1 - if swing.pierce <= 0 then - projectiles.server.destroy(swing.id) +function physics.server:load() + self.beginContact = function(a, b, coll) + for _, v in pairs{{a, b}, {b, a}} do + local fixa = v[1] + local fixb = v[2] + local uda = fixa:getUserData() or {} + local udb = fixb:getUserData() or {} + if uda.type == 'playerSwing' and udb.enemy then + self:postUpdatePush(function() + local swing = projectiles.server.container[uda.id] + local enemy = udb + if swing and enemy and not enemy.hitBy[swing.id] then + enemy.hitBy[swing.id] = true + enemy:damage(swing.damage, swing.playerId) + swing.pierce = swing.pierce - 1 + if swing.pierce <= 0 then + projectiles.server.destroy(swing.id) + end end - end - end) + end) + end end end + self.endContact = function(a, b, coll) end + self.preSolve = function(a, b, coll) end + self.postSolve = function(a, b, coll, normalImpulse, tangentImpulse) end + + if self.world then self.world:destroy() end + self.world = love.physics.newWorld(0, 0, true) + self.world:setCallbacks(self.beginContact, + self.endContact, self.preSolve, self.postSolve) end -function physics.server.endContact(a, b, coll) - -end - -function physics.server.preSolve(a, b, coll) - +function physics.server:update(dt) + self.world:update(dt) + for i, v in pairs(self.postUpdateQueue) do + v() + self.postUpdateQueue[i] = nil + end end -function physics.server.postSolve(a, b, coll, normalImpulse, tangentImpulse) - +-- can't do stuff like destroying fixtures in callbacks - queue for update +function physics.server:postUpdatePush(f) + table.insert(self.postUpdateQueue, f) end -function physics.client.load() - if physics.client.world then physics.client.world:destroy() end - physics.client.world = love.physics.newWorld(0, 0, true) - physics.client.world:setCallbacks(physics.client.beginContact, - physics.client.endContact, physics.client.preSolve, physics.client.postSolve) -end - -function physics.client.update(dt) - physics.client.world:update(dt) - for i, v in pairs(physics.client.postUpdateQueue) do - v() - physics.client.postUpdateQueue[i] = nil +function physics.client:new(o) + o = o or {} + for k, v in pairs(defaults.client) do + if o[k] == nil then o[k] = v() end end + setmetatable(o, self) + self.__index = self + return o end -function physics.client.postUpdatePush(f) - table.insert(physics.client.postUpdateQueue, f) -end - -function physics.client.beginContact(a, b, coll) - for _, v in pairs{{a, b}, {b, a}} do - local fixa = v[1] - local fixb = v[2] - local uda = fixa:getUserData() or {} - local udb = fixb:getUserData() or {} - if uda.type == 'slimeBall' and udb.id == playerController.player.id then - physics.client.postUpdatePush(function() - udb:damage(uda.damage) - slimeBalls.destroy(uda.id) - end) +function physics.client:load() + self.beginContact = function(a, b, coll) + for _, v in pairs{{a, b}, {b, a}} do + local fixa = v[1] + local fixb = v[2] + local uda = fixa:getUserData() or {} + local udb = fixb:getUserData() or {} + if uda.type == 'slimeBall' and udb.id == playerController.player.id then + self:postUpdatePush(function() + udb:damage(uda.damage) + slimeBalls.destroy(uda.id) + end) + end end end + self.endContact = function(a, b, coll) end + self.preSolve = function(a, b, coll) end + self.postSolve = function(a, b, coll, normalImpulse, tangentImpulse) end + + if self.world then self.world:destroy() end + self.world = love.physics.newWorld(0, 0, true) + self.world:setCallbacks(self.beginContact, + self.endContact, self.preSolve, self.postSolve) end -function physics.client.endContact(a, b, coll) - -end - -function physics.client.preSolve(a, b, coll) - +function physics.client:update(dt) + self.world:update(dt) + for i, v in pairs(self.postUpdateQueue) do + v() + self.postUpdateQueue[i] = nil + end end -function physics.client.postSolve(a, b, coll, normalImpulse, tangentImpulse) - +function physics.client:postUpdatePush(f) + table.insert(self.postUpdateQueue, f) end diff --git a/playerController.lua b/playerController.lua index 69715dc..fd8a8cb 100644 --- a/playerController.lua +++ b/playerController.lua @@ -1,5 +1,10 @@ -playerController = {} +playerController = { + interactRange = 30, + closestBag = {id=nil, dist=nil, open=false}, + hoveredItem = nil, + heldItem = {bagId=nil, slotId=nil, offset={x=0, y=0}} +} function playerController.load() playerController.player = entities.client.defs.player:new{isLocalPlayer=true}:spawn() @@ -10,6 +15,7 @@ function playerController.update(dt) local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) + -- update player input local inputState = {keyboard={}, mouse={}} for _, key in pairs{'w', 'a', 's', 'd', 'lshift'} do inputState.keyboard[key] = love.keyboard.isScancodeDown(key) and not chat.active @@ -21,16 +27,138 @@ function playerController.update(dt) local p = playerController.player p:setInputState(inputState) + -- update camera local dx = mx - gsx/2 local dy = my - gsy/2 local a = math.atan2(dx, dy) - math.pi/2 local d = math.min(math.sqrt(dx^2 + dy^2), gsy/2) camera.x = lume.round(p.body:getX()) + lume.round(math.cos(a)*d/6) camera.y = lume.round(p.body:getY() - 14) + lume.round(-math.sin(a)*d/6) + + -- update closest lootBag, heldItem + local closestBag = playerController.closestBag + local px, py = playerController.player.body:getPosition() + local lastId = closestBag.id + closestBag.id = nil + closestBag.dist = nil + for _, bag in pairs(client.currentState.lootBags) do + local dist = math.sqrt((bag.x - px)^2 + (bag.y - py)^2) + if not closestBag.dist or dist < closestBag.dist then + closestBag.id = bag.id + closestBag.dist = dist + end + end + -- todo: open bag should be able to not be closest + if not closestBag.id or closestBag.id ~= lastId or closestBag.dist > playerController.interactRange then + closestBag.open = false + end + local heldItem = playerController.heldItem + if heldItem.bagId ~= 'inventory' and (not closestBag.id + or closestBag.id ~= heldItem.bagId or closestBag.dist > playerController.interactRange) then + heldItem.bagId = nil + heldItem.slotId = nil + end + playerController.hoveredItem = nil end function playerController.mousepressed(x, y, btn) + local mx, my = window2game(x, y) + mx, my = lume.round(mx), lume.round(my) + wmx, wmy = camera:screen2world(mx, my) + + -- lootBags + local closestBag = playerController.closestBag + if closestBag.id and closestBag.open then + local bag = client.currentState.lootBags[closestBag.id] + local img = gfx.ui.bag + local bmx = wmx - (lume.round(bag.x) - lume.round(img:getWidth()/2)) + local bmy = wmy - (lume.round(bag.y) - img:getHeight() - 20) + for slotId, slot in ipairs(lootBagSlots) do + if bmx >= slot.x and bmx <= slot.x + slot.w + and bmy >= slot.y and bmy <= slot.y + slot.h then + uiMouseDown = true + if bag.items[slotId] then + if btn == 1 then + local heldItem = playerController.heldItem + heldItem.bagId = bag.id + heldItem.slotId = slotId + heldItem.offset.x = slot.x - bmx + heldItem.offset.y = slot.y - bmy + elseif btn == 2 then + local p = playerController.player + for invSlotId, _ in ipairs(hud.inventorySlots) do + if p.inventory.items[invSlotId] == nil then + client.moveItem{ + from = { + bagId = bag.id, + slotId = slotId + }, + to = { + bagId = p.inventory.id, + slotId = invSlotId + } + } + break + end + end + end + end + break + end + end + end + + -- player attack if not uiMouseDown then playerController.player:mousepressed(x, y, btn) end end + +function playerController.mousereleased(x, y, btn) + local mx, my = window2game(x, y) + mx, my = lume.round(mx), lume.round(my) + wmx, wmy = camera:screen2world(mx, my) + + -- lootBags + local closestBag = playerController.closestBag + local heldItem = playerController.heldItem + if closestBag.id and closestBag.open and heldItem.bagId then + local bagFrom = client.currentState.lootBags[heldItem.bagId] + if heldItem.bagId == 'inventory' then + bagFrom = playerController.player.inventory + end + local bagTo = client.currentState.lootBags[closestBag.id] + local img = gfx.ui.bag + local bmx = wmx - (lume.round(bagTo.x) - lume.round(img:getWidth()/2)) + local bmy = wmy - (lume.round(bagTo.y) - img:getHeight() - 20) + for slotId, slot in ipairs(lootBagSlots) do + if bmx >= slot.x and bmx <= slot.x + slot.w + and bmy >= slot.y and bmy <= slot.y + slot.h then + client.moveItem{ + from = { + bagId = bagFrom.id, + slotId = heldItem.slotId + }, + to = { + bagId = bagTo.id, + slotId = slotId + } + } + -- move clientside before response (will be corrected/affirmed) + local temp = bagTo.items[slotId] + bagTo.items[slotId] = bagFrom.items[heldItem.slotId] + bagFrom.items[heldItem.slotId] = temp + break + end + end + end +end + +function playerController.keypressed(k, scancode, isrepeat) + if scancode == 'e' and not isrepeat then + local closestBag = playerController.closestBag + if closestBag.id and closestBag.dist < playerController.interactRange then + closestBag.open = not closestBag.open + end + end +end diff --git a/portals.lua b/portals.lua new file mode 100644 index 0000000..0ce4f23 --- /dev/null +++ b/portals.lua @@ -0,0 +1,80 @@ + +portals = { + server = { + container = {} + }, + client = {} +} + +function portals.server.spawn(data) + local defaults = { + x = 0, y = 0, + -- life defaults to nil (permanent) + } + for k, v in pairs(defaults) do + if data[k] == nil then data[k] = v end + end + + data.id = lume.uuid() + data.spawnTime = gameTime + + portals.server.container[data.id] = data + local state = { + id = data.id, + x = data.x, y = data.y, + life = data.life + } + server.currentState.portals[data.id] = state + server.added.portals[data.id] = state +end + +function portals.server.destroy(id) + portals.server.container[id] = nil + server.currentState.portals[id] = nil + server.removed.portals[id] = id +end + +function portals.server.reset() + for k, v in pairs(portals.server.container) do + portals.server.destroy(k) + end +end + +function portals.server.update(dt) + for k, v in pairs(portals.server.container) do + if gameTime - v.spawnTime > v.life then + portals.server.destroy(k) + end + end +end + + + +function portals.client.keypressed(k, scancode, isrepeat) + if scancode == 'e' and not isrepeat then + -- todo: closest like lootBags + local px, py = playerController.player.body:getPosition() + for k, v in pairs(client.currentState.portals) do + if (px-v.x)^2+(py-v.y)^2 < 24^2 then + client.usePortal{id=v.id} + end + end + end +end + +function portals.client.draw() + for _, v in pairs(client.currentState.portals) do + scene.add{ + draw = function() + love.graphics.setColor(0.2, 0.2, 0.8) + local px, py = playerController.player.body:getPosition() + if (px-v.x)^2+(py-v.y)^2 < 24^2 then + love.graphics.setColor(0.5, 0.5, 0.9) + end + local w, h = 16, 24 + love.graphics.rectangle('fill', lume.round(v.x - w/2), lume.round(v.y - h), w, h) + end, + y = v.y + } + end +end diff --git a/projectiles.lua b/projectiles.lua index 0ebf277..8b013f5 100644 --- a/projectiles.lua +++ b/projectiles.lua @@ -23,7 +23,7 @@ function projectiles.server.spawn(data) data.id = lume.uuid() data.type = 'playerSwing' data.spawnTime = gameTime - data.body = love.physics.newBody(physics.server.world, data.x, data.y, 'dynamic') + data.body = love.physics.newBody(serverRealm.physics.world, data.x, data.y, 'dynamic') data.polys = { {-0.2, 0.6, 0, 0.3, -0.1, 0.2}, {0, 0.3, 0.1, 0, 0, -0.3, -0.1, -0.2, -0.1, 0.2}, diff --git a/realm.lua b/realm.lua new file mode 100644 index 0000000..72c9971 --- /dev/null +++ b/realm.lua @@ -0,0 +1,145 @@ + +realm = { + server = {}, + client = {} +} + +local defaults = {server={}, client={}} +for _, sc in pairs{'server', 'client'} do + defaults[sc].id = function() return lume.uuid() end + defaults[sc].name = function() return 'realm' end + for _, v in pairs{'entities', 'projectiles', 'slimeBalls', 'lootBags', 'portals'} do + defaults[sc][v] = function() return {} end + end + for _, v in pairs{{'physics', physics}, {'world', world}} do + defaults[sc][v[1]] = function() return v[2][sc]:new() end + end +end + + + +function realm.server:new(o) + o = o or {} + for k, v in pairs(defaults.server) do + if o[k] == nil then o[k] = v() end + end + setmetatable(o, self) + self.__index = self + return o +end + +function realm.server:load() + self.physics:load() +end + +function realm.server:update(dt) + self.physics:update(dt) + for _, bag in pairs(self.lootBags) do + if bag.life and gameTime - bag.spawnTime > bag.life then + bag:destroy() + end + end +end + +function realm.server:destroy() + self.world:destroy() + for _, v in pairs(self.lootBags) do + v:destroy() + end +end + + + +function realm.client:new(o) + o = o or {} + for k, v in pairs(defaults.client) do + if o[k] == nil then o[k] = v() end + end + setmetatable(o, self) + self.__index = self + return o +end + +function realm.client:load() + self.physics:load() +end + +function realm.client:update(dt) + self.physics:update(dt) + self.world:update(dt) +end + +function realm.client:destroy() + self.world:destroy() + for _, v in pairs(self.lootBags) do + v:destroy() + end +end + +function realm.client:draw() + local mx, my = window2game(love.mouse.getPosition()) + mx, my = lume.round(mx), lume.round(my) + wmx, wmy = camera:screen2world(mx, my) + + self.world:draw() + + for _, bag in pairs(self.lootBags) do + scene.add{ + draw = function() + local tint = 1 + local outlineColor = {0, 0, 0, 1} + if bag.life and client.serverTime - bag.spawnTime > bag.life - 3 then + local t = (client.serverTime - bag.spawnTime - bag.life + 3)/3 + tint = math.cos(t*5*math.pi)/4 + 1/4 + (1-t)/2 + tint = tint/2 + 1/2 + outlineColor = {0.8, 0, 0, 1} + end + local closestBag = playerController.closestBag + if bag.id == closestBag.id and closestBag.dist < playerController.interactRange then + outlineColor = {0.8, 0.8, 0.8, 1} + end + love.graphics.setColor(tint, tint, tint) + love.graphics.push() + love.graphics.translate(lume.round(bag.x), lume.round(bag.y)) + local img = gfx.items[bag.type] + local _shader = love.graphics.getShader() + love.graphics.setShader(shaders.outline) + shaders.outline:send('stepSize', {1/img:getWidth(), 1/img:getHeight()}) + shaders.outline:send('outlineColor', outlineColor) + love.graphics.draw(img, 0, 0, 0, 1, 1, lume.round(img:getWidth()/2), img:getHeight()) + love.graphics.setShader(_shader) + if bag.id == closestBag.id and closestBag.open then + local img = gfx.ui.bag + love.graphics.push() + love.graphics.translate(-lume.round(img:getWidth()/2), -img:getHeight() - 20) + love.graphics.setColor(1, 1, 1) + love.graphics.draw(img, 0, 0) + local bmx = wmx - (lume.round(bag.x) - lume.round(img:getWidth()/2)) + local bmy = wmy - (lume.round(bag.y) - img:getHeight() - 20) + for slotId, slot in ipairs(lootBagSlots) do + local item = items.client.getItem(bag.items[slotId]) + if bmx >= slot.x and bmx <= slot.x + slot.w + and bmy >= slot.y and bmy <= slot.y + slot.h then + if item then + cursor.cursor = cursor.hand + playerController.hoveredItem = item + end + love.graphics.setColor(1, 1, 1, 0.4) + love.graphics.rectangle('fill', slot.x, slot.y, slot.w, slot.h) + end + local heldItem = playerController.heldItem + if not (heldItem.bagId == bag.id and heldItem.slotId == slotId) then + if item then + love.graphics.setColor(1, 1, 1) + love.graphics.draw(gfx.items[item.imageId], slot.x, slot.y) + end + end + end + love.graphics.pop() + end + love.graphics.pop() + end, + y = bag.y + } + end +end diff --git a/server.lua b/server.lua index 7831598..4124a21 100644 --- a/server.lua +++ b/server.lua @@ -65,11 +65,11 @@ function server.start(port, singleplayer) moveItem = function(self, data, clientId) -- todo: validation local p = server.currentState.players[clientId] - local bagFrom = server.currentState.lootBags[data.from.bagId] + local bagFrom = serverRealm.lootBags[data.from.bagId] if data.from.bagId == 'inventory' then bagFrom = p.inventory end - local bagTo = server.currentState.lootBags[data.to.bagId] + local bagTo = serverRealm.lootBags[data.to.bagId] if data.to.bagId == 'inventory' then bagTo = p.inventory end @@ -88,15 +88,15 @@ function server.start(port, singleplayer) end end if empty then - lootBags.server.destroy(bagFrom.id) + bagFrom:destroy() end end -- inventory sent in player update if data.from.bagId ~= 'inventory' then - server.nutServer:sendRPC('bagUpdate', bitser.dumps(bagFrom)) + server.nutServer:sendRPC('bagUpdate', bitser.dumps(bagFrom:serialize())) end if data.to.bagId ~= 'inventory' then - server.nutServer:sendRPC('bagUpdate', bitser.dumps(bagTo)) + server.nutServer:sendRPC('bagUpdate', bitser.dumps(bagTo:serialize())) end end end @@ -107,7 +107,7 @@ function server.start(port, singleplayer) if item then local itemDropped = false local bags = {} - for _, bag in pairs(lootBags.server.container) do + for _, bag in pairs(serverRealm.lootBags) do table.insert(bags, bag) end local sortedBags = isort(bags, function(a, b) @@ -117,11 +117,11 @@ function server.start(port, singleplayer) end) for _, bag in ipairs(sortedBags) do local dist = math.sqrt((bag.x - p.x)^2 + (bag.y - p.y)^2) - if dist < lootBags.client.openRange then - for bagSlotId, _ in ipairs(lootBags.client.slots) do + if dist < playerController.interactRange then + for bagSlotId, _ in ipairs(lootBagSlots) do if bag.items[bagSlotId] == nil then bag.items[bagSlotId] = item - server.nutServer:sendRPC('bagUpdate', bitser.dumps(bag)) + server.nutServer:sendRPC('bagUpdate', bitser.dumps(bag:serialize())) itemDropped = true break end @@ -130,21 +130,41 @@ function server.start(port, singleplayer) if itemDropped then break end end if not itemDropped then - lootBags.server.spawn{ + lootBag.server:new{ + realm = serverRealm, x = p.x, y = p.y, items = {item}, life = 30 - } + }:spawn() itemDropped = true end p.inventory.items[data.slotId] = nil -- inventory sent in player update end end, + useItem = function(self, data, clientId) + local p = server.currentState.players[clientId] + local itemId = p.inventory.items[data.slotId] + local item = items.server.getItem(itemId) + if item then + if item.imageId == 'apple' then + server.nutServer:sendRPC('healPlayer', bitser.dumps{hp=20}, clientId) + p.inventory.items[data.slotId] = nil + end + end + end, getWorldChunk = function(self, data, clientId) - local chunk = world.server.getChunk(data.x, data.y) + local chunk = serverRealm.world:getChunk(data.x, data.y) local res = {x=data.x, y=data.y, chunk=chunk} server.nutServer:sendRPC('setWorldChunk', bitser.dumps(res), clientId) + end, + usePortal = function(self, data, clientId) + local portal = portals.server.container[data.id] + if portal then + local tx = portal.x + lume.random(-128, 128) + local ty = portal.y + lume.random(-128, 128) + server.nutServer:sendRPC('teleportPlayer', bitser.dumps{x=tx, y=ty}, clientId) + end end } for k, v in pairs(bitserRPCs) do @@ -185,7 +205,7 @@ function server.start(port, singleplayer) stateUpdate.entities[v.id] = v end end - -- no lootBags updates + -- no lootBags or portals updates self:sendRPC('stateUpdate', bitser.dumps(stateUpdate)) end) server.nutServer:start() @@ -204,18 +224,17 @@ function server.start(port, singleplayer) if server.currentState then projectiles.server.reset() entities.server.reset() - world.server.reset() + serverRealm:destroy() items.server.reset() - lootBags.server.reset() + portals.server.reset() end server.currentState = server.newState() - physics.server.load() - --entities.server.load() + serverRealm:load() end function server.newState() - return {players={}, entities={}, projectiles={}, lootBags={}} + return {players={}, entities={}, projectiles={}, lootBags={}, portals={}} end function server.addPlayer(name, clientId) @@ -275,10 +294,10 @@ function server.update(dt) server.nutServer:update(dt) if not server.paused then - physics.server.update(dt) + serverRealm:update(dt) projectiles.server.update(dt) entities.server.update(dt) - lootBags.server.update(dt) + portals.server.update(dt) end end diff --git a/slimeBalls.lua b/slimeBalls.lua index 2a976bb..5b3d207 100644 --- a/slimeBalls.lua +++ b/slimeBalls.lua @@ -28,7 +28,7 @@ function slimeBalls.spawn(t) t.type = 'slimeBall' t.color = slimeBalls.slimeType2color[t.slimeType] t.spawnTime = gameTime - t.body = love.physics.newBody(physics.client.world, t.x, t.y, 'dynamic') + t.body = love.physics.newBody(clientRealm.physics.world, t.x, t.y, 'dynamic') t.shape = love.physics.newCircleShape(5) t.fixture = love.physics.newFixture(t.body, t.shape, 1) t.fixture:setUserData(t) diff --git a/world.lua b/world.lua index 7a28447..c927a8d 100644 --- a/world.lua +++ b/world.lua @@ -1,58 +1,77 @@ world = { - server = { - chunks = {}, - }, - client = { - chunks = {}, - chunkImages = {}, -- 1 pix/tile (minimap) - chunkIdImages = {} - } + server = {}, + client = {} } -world.chunkSize = 8 -world.tileColors = { - {98/255, 195/255, 116/255}, - {251/255, 228/255, 125/255}, - {98/255, 98/255, 98/255}, - {41/255, 137/255, 214/255}, - {73/255, 73/255, 73/255} -} +local chunkSize = 8 -world.server.chunkCanvas = love.graphics.newCanvas(world.chunkSize, world.chunkSize) +local defaults = {server={}, client={}} +for _, sc in pairs{'server', 'client'} do + defaults[sc].chunks = function() return {} end + defaults[sc].chunkSize = function() return chunkSize end +end +defaults.server.chunkCanvas = function() + return love.graphics.newCanvas(chunkSize, chunkSize) +end +defaults.client.chunkImages = function() return {} end +defaults.client.chunkIdImages = function() return {} end +defaults.client.tileColors = function() + return { + {98/255, 195/255, 116/255}, + {251/255, 228/255, 125/255}, + {98/255, 98/255, 98/255}, + {41/255, 137/255, 214/255}, + {73/255, 73/255, 73/255} + } +end -- max tiles visible (ceil(x)+1) + sides for blending (+2) local w, h = math.ceil(gsx/15) + 1 + 2, math.ceil(gsy/15) + 1 + 2 -world.client.tileIdCanvas = love.graphics.newCanvas(w, h) +defaults.client.tileIdCanvas = function() + return love.graphics.newCanvas(w, h) +end shaders.mapRender:send('tileIdRes', {w, h}) -function world.server.getChunk(x, y) - if world.server.chunks[x] and world.server.chunks[x][y] then - return world.server.chunks[x][y] + + +function world.server:new(o) + o = o or {} + for k, v in pairs(defaults.server) do + if o[k] == nil then o[k] = v() end + end + setmetatable(o, self) + self.__index = self + return o +end + +function world.server:getChunk(x, y) + if self.chunks[x] and self.chunks[x][y] then + return self.chunks[x][y] else - if world.server.chunks[x] == nil then world.server.chunks[x] = {} end + if self.chunks[x] == nil then self.chunks[x] = {} end local _canvas = love.graphics.getCanvas() local _shader = love.graphics.getShader() - love.graphics.setCanvas(world.server.chunkCanvas) + love.graphics.setCanvas(self.chunkCanvas) love.graphics.clear() love.graphics.setShader(shaders.mapGen) - shaders.mapGen:send('camPos', {x*world.chunkSize, y*world.chunkSize}) + shaders.mapGen:send('camPos', {x*self.chunkSize, y*self.chunkSize}) love.graphics.push() love.graphics.origin() - love.graphics.rectangle('fill', 0, 0, world.chunkSize, world.chunkSize) + love.graphics.rectangle('fill', 0, 0, self.chunkSize, self.chunkSize) love.graphics.pop() love.graphics.setCanvas(_canvas) love.graphics.setShader(_shader) - local imageData = world.server.chunkCanvas:newImageData() - world.server.chunks[x][y] = {} - local chunk = world.server.chunks[x][y] - for i=1, world.chunkSize do + local imageData = self.chunkCanvas:newImageData() + self.chunks[x][y] = {} + local chunk = self.chunks[x][y] + for i=1, self.chunkSize do if chunk[i] == nil then chunk[i] = {} end - for j=1, world.chunkSize do + for j=1, self.chunkSize do local v = imageData:getPixel(i-1, j-1) v = lume.round(v*255) chunk[i][j] = v @@ -62,80 +81,90 @@ function world.server.getChunk(x, y) end end -function world.server.getTile(x, y) +function world.server:getTile(x, y) local tx = math.floor(x/15) local ty = math.floor(y/15) - local cx = math.floor(tx/world.chunkSize) - local cy = math.floor(ty/world.chunkSize) - tx = tx - cx*world.chunkSize + 1 - ty = ty - cy*world.chunkSize + 1 - if world.server.chunks[cx] and world.server.chunks[cx][cy] then - return world.server.chunks[cx][cy][tx][ty] + local cx = math.floor(tx/self.chunkSize) + local cy = math.floor(ty/self.chunkSize) + tx = tx - cx*self.chunkSize + 1 + ty = ty - cy*self.chunkSize + 1 + if self.chunks[cx] and self.chunks[cx][cy] then + return self.chunks[cx][cy][tx][ty] end end -function world.server.reset() - world.server.chunks = {} +function world.server:destroy() + self.chunks = {} end -function world.client.setChunk(x, y, chunk) - if world.client.chunks[x] == nil then world.client.chunks[x] = {} end - world.client.chunks[x][y] = chunk - local imageData = love.image.newImageData(world.chunkSize, world.chunkSize) - local idImageData = love.image.newImageData(world.chunkSize, world.chunkSize) - for i=1, world.chunkSize do - for j=1, world.chunkSize do +function world.client:new(o) + o = o or {} + for k, v in pairs(defaults.client) do + if o[k] == nil then o[k] = v() end + end + setmetatable(o, self) + self.__index = self + return o +end + +function world.client:setChunk(x, y, chunk) + if self.chunks[x] == nil then self.chunks[x] = {} end + self.chunks[x][y] = chunk + local imageData = love.image.newImageData(self.chunkSize, self.chunkSize) + local idImageData = love.image.newImageData(self.chunkSize, self.chunkSize) + for i=1, self.chunkSize do + for j=1, self.chunkSize do local v = chunk[i][j] - imageData:setPixel((i-1), (j-1), unpack(world.tileColors[v] or {0,0,0})) + imageData:setPixel((i-1), (j-1), unpack(self.tileColors[v] or {0,0,0})) idImageData:setPixel((i-1), (j-1), v/255, 0, 0) end end - if world.client.chunkImages[x] == nil then world.client.chunkImages[x] = {} end - world.client.chunkImages[x][y] = love.graphics.newImage(imageData) - if world.client.chunkIdImages[x] == nil then world.client.chunkIdImages[x] = {} end - world.client.chunkIdImages[x][y] = love.graphics.newImage(idImageData) + if self.chunkImages[x] == nil then self.chunkImages[x] = {} end + self.chunkImages[x][y] = love.graphics.newImage(imageData) + if self.chunkIdImages[x] == nil then self.chunkIdImages[x] = {} end + self.chunkIdImages[x][y] = love.graphics.newImage(idImageData) end -function world.client.getTile(x, y) +function world.client:getTile(x, y) local tx = math.floor(x/15) local ty = math.floor(y/15) - local cx = math.floor(tx/world.chunkSize) - local cy = math.floor(ty/world.chunkSize) - tx = tx - cx*world.chunkSize + 1 - ty = ty - cy*world.chunkSize + 1 - if world.client.chunks[cx] and world.client.chunks[cx][cy] then - return world.client.chunks[cx][cy][tx][ty] + local cx = math.floor(tx/self.chunkSize) + local cy = math.floor(ty/self.chunkSize) + tx = tx - cx*self.chunkSize + 1 + ty = ty - cy*self.chunkSize + 1 + if self.chunks[cx] and self.chunks[cx][cy] then + return self.chunks[cx][cy][tx][ty] end end -function world.client.reset() - world.client.chunks = {} +function world.client:destroy() + self.chunks = {} end -function world.client.update(dt) +function world.client:update(dt) -- get visible chunks (+ pad) if nil local p = playerController.player - local cx1 = math.floor((p.x - 465)/15/world.chunkSize) - 1 - local cx2 = math.floor((p.x + 465)/15/world.chunkSize) + 1 - local cy1 = math.floor((p.y - 465)/15/world.chunkSize) - 1 - local cy2 = math.floor((p.y + 465)/15/world.chunkSize) + 1 + local cx1 = math.floor((p.x - 465)/15/self.chunkSize) - 1 + local cx2 = math.floor((p.x + 465)/15/self.chunkSize) + 1 + local cy1 = math.floor((p.y - 465)/15/self.chunkSize) - 1 + local cy2 = math.floor((p.y + 465)/15/self.chunkSize) + 1 for cx=cx1, cx2 do for cy=cy1, cy2 do - if not world.client.chunks[cx] or not world.client.chunks[cx][cy] then + if not self.chunks[cx] or not self.chunks[cx][cy] then client.nutClient:sendRPC('getWorldChunk', bitser.dumps{x=cx, y=cy}) end end end end -function world.client.draw() +function world.client:draw() local _canvas = love.graphics.getCanvas() local _shader = love.graphics.getShader() -- set id canvas - love.graphics.setCanvas(world.client.tileIdCanvas) + love.graphics.setCanvas(self.tileIdCanvas) love.graphics.clear() love.graphics.setShader() love.graphics.setColor(1, 1, 1) @@ -146,15 +175,15 @@ function world.client.draw() local tx2 = math.floor((camera.x + camera.ssx/2)/15) + 1 local ty1 = math.floor((camera.y - camera.ssy/2)/15) - 1 local ty2 = math.floor((camera.y + camera.ssy/2)/15) + 1 - local cx1 = math.floor(tx1/world.chunkSize) - local cx2 = math.floor(tx2/world.chunkSize) - local cy1 = math.floor(ty1/world.chunkSize) - local cy2 = math.floor(ty2/world.chunkSize) - local idImgs = world.client.chunkIdImages + local cx1 = math.floor(tx1/self.chunkSize) + local cx2 = math.floor(tx2/self.chunkSize) + local cy1 = math.floor(ty1/self.chunkSize) + local cy2 = math.floor(ty2/self.chunkSize) + local idImgs = self.chunkIdImages for cx=cx1, cx2 do for cy=cy1, cy2 do if idImgs[cx] and idImgs[cx][cy] then - love.graphics.draw(idImgs[cx][cy], cx*world.chunkSize - tx1, cy*world.chunkSize - ty1) + love.graphics.draw(idImgs[cx][cy], cx*self.chunkSize - tx1, cy*self.chunkSize - ty1) end end end @@ -163,7 +192,7 @@ function world.client.draw() -- draw world love.graphics.setShader(shaders.mapRender) - shaders.mapRender:send('tileIds', world.client.tileIdCanvas) + shaders.mapRender:send('tileIds', self.tileIdCanvas) shaders.mapRender:send('camPos', { camera.x - camera.ssx/2, camera.y - camera.ssy/2 @@ -183,15 +212,15 @@ function world.client.draw() -- test shader validity if false then love.graphics.setColor(1, 1, 1, 0.4) - for cx, chunkCol in pairs(world.client.chunkImages) do -- chunks[x] + for cx, chunkCol in pairs(self.chunkImages) do -- chunks[x] for cy, img in pairs(chunkCol) do - love.graphics.draw(img, cx*world.chunkSize*15, cy*world.chunkSize*15, 0, 15, 15) + love.graphics.draw(img, cx*self.chunkSize*15, cy*self.chunkSize*15, 0, 15, 15) end end end end -function world.client.drawMinimap() +function world.client:drawMinimap() local _canvas = love.graphics.getCanvas() local _shader = love.graphics.getShader() -- todo: 2x @@ -213,12 +242,12 @@ function world.client.drawMinimap() end, 'replace', 1) love.graphics.setStencilTest('greater', 0) love.graphics.setColor(1, 1, 1) - for cx, chunkCol in pairs(world.client.chunkImages) do + for cx, chunkCol in pairs(self.chunkImages) do for cy, img in pairs(chunkCol) do - local dx = cx*world.chunkSize - px - local dy = cy*world.chunkSize - py - if dx + world.chunkSize >= -30 and dx <= 30 - and dy + world.chunkSize >= -31 and dy < 31 then + local dx = cx*self.chunkSize - px + local dy = cy*self.chunkSize - py + if dx + self.chunkSize >= -30 and dx <= 30 + and dy + self.chunkSize >= -31 and dy < 31 then love.graphics.draw(img, x + 30 + dx, y + 31 + dy) end end