diff --git a/README.md b/README.md index e98566d..cf10e2f 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ Open World RPG ## Running Windows builds can be found on the [releases](https://github.com/parameterized/tier/releases) page -Instructions for running Love2D games from source can be found [here](https://love2d.org/wiki/Getting_Started) +Instructions for running Love2D games from source can be found [here](https://love2d.org/wiki/Getting_Started). If you're on Windows, the easiest way to run is to drag the folder containing main.lua onto either love.exe or a shortcut to love.exe (C:\\Program Files\\LOVE\\love.exe) ## Controls - WASD to move - Left Shift to sprint -- Left Mouse Button to attack +- Left click to attack - Right click to transfer item - Shift-click or double-click to use item - E to interact diff --git a/client.lua b/client.lua index 1325f0c..7d47ad3 100644 --- a/client.lua +++ b/client.lua @@ -87,6 +87,7 @@ function client.connect(ip, port) healPlayer = function(self, data) local p = playerController.player p.hp = math.min(p.hp + data.hp, p.hpMax) + sound.play('heal') end, damageText = function(self, data) damageText.add(data) diff --git a/entities.lua b/entities.lua index 7c1fca6..61476da 100644 --- a/entities.lua +++ b/entities.lua @@ -15,8 +15,9 @@ entities.activeRadius = 500 entities.chunkSize = 8 local entityDefs = {} -for _, v in ipairs{'player', 'slime', 'tree', 'wall', 'sorcerer', 'spoder', 'stingy', 'zombie', 'ant', -'newMonster1', 'newMonster2', 'mudskipper', 'mudskipperEvolved', 'godex'} do +for _, v in ipairs{'player', 'slime', 'sorcerer', 'spoder', 'stingy', 'zombie', 'ant', +'newMonster1', 'newMonster2', 'mudskipper', 'mudskipperEvolved', 'godex', +'tree', 'wall', 'bush', 'bigRock', 'smallRock'} do table.insert(entityDefs, require('entityDefs.' .. v)) end @@ -62,15 +63,6 @@ function entities.server.update(dt) end end end - -- spawn trees - if math.random() < 0.5 then - local x = (cx*entities.chunkSize + math.random()*entities.chunkSize)*15 - local y = (cy*entities.chunkSize + math.random()*entities.chunkSize)*15 - -- if on grass - if serverRealm.world:getTile(x, y) == tile2id['grass'] then - entities.server.defs.tree:new{x=x, y=y}:spawn() - end - end -- spawn walls for i=1, entities.chunkSize do for j=1, entities.chunkSize do @@ -81,6 +73,29 @@ function entities.server.update(dt) end end end + -- spawn trees, bush, rocks + for _=1, 3 do + if math.random() < 0.5 then + local x = (cx*entities.chunkSize + math.random()*entities.chunkSize)*15 + local y = (cy*entities.chunkSize + math.random()*entities.chunkSize)*15 + -- if on grass and grass surrounding + local onGrass = true + for i=-1, 1 do + for j=-1, 1 do + if serverRealm.world:getTile(x + i*15, y + j*15) ~= tile2id['grass'] then + onGrass = false + break + end + end + if not onGrass then break end + end + if onGrass then + local choices = {tree=40, bush=30, bigRock=10, smallRock=20} + choice = lume.weightedchoice(choices) + entities.server.defs[choice]:new{x=x, y=y}:spawn() + end + end + end end end end diff --git a/entityDefs/ant.lua b/entityDefs/ant.lua index 9526005..4cd76eb 100644 --- a/entityDefs/ant.lua +++ b/entityDefs/ant.lua @@ -100,46 +100,7 @@ function ant.server:update(dt) end function ant.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -225,8 +186,7 @@ function ant.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/entityDefs/bigRock.lua b/entityDefs/bigRock.lua new file mode 100644 index 0000000..adbafd1 --- /dev/null +++ b/entityDefs/bigRock.lua @@ -0,0 +1,91 @@ + +local base = require 'entityDefs._base' +local bigRock = { + server = base.server:new(), + client = base.client:new() +} + +for _, sc in ipairs{'server', 'client'} do + bigRock[sc].newDefaults = function() + local t = { + id = lume.uuid(), + x = 0, y = 0 + } + if sc == 'server' then + t.base = base.server + t.realm = serverRealm + elseif sc == 'client' then + t.base = base.client + t.realm = clientRealm + end + return t + end + + bigRock[sc].spawn = function(self) + self.body = love.physics.newBody(self.realm.physics.world, self.x, self.y, 'static') + self.polys = { + {0.075, 0.05, 0.875, 0.05, 0.975, 0.15, 0.025, 0.15} + } + -- transform - todo: load from file already transformed + local img = gfx.environment.bigRock + for _, v in pairs(self.polys) do + for i2, v2 in pairs(v) do + if (i2-1) % 2 == 0 then -- x + v[i2] = v2*img:getWidth() - img:getWidth()/2 + else + v[i2] = v2*img:getWidth()*-1 + end + end + end + self.shapes = {} + self.fixtures = {} + for _, v in pairs(self.polys) do + local shape = love.physics.newPolygonShape(unpack(v)) + table.insert(self.shapes, shape) + local fixture = love.physics.newFixture(self.body, shape, 1) + table.insert(self.fixtures, fixture) + fixture:setUserData(self) + fixture:setCategory(4) + end + return self.base.spawn(self) + end + + bigRock[sc].destroy = function(self) + if self.fixtures then + for _, v in pairs(self.fixtures) do + if not v:isDestroyed() then v:destroy() end + end + end + if self.body and not self.body:isDestroyed() then + self.body:destroy() + end + self.base.destroy(self) + end + + bigRock[sc].type = 'bigRock' +end + + + +function bigRock.client:draw() + local img = gfx.environment.bigRock + local vx, vy = self.body:getPosition() + vx, vy = lume.round(vx), lume.round(vy) + local p = playerController.player + local px, py = lume.round(p.x), lume.round(p.y) + local pdx = px - vx + local pdy = py - vy + local a = 1 + if math.abs(px - vx) < lume.round(img:getWidth()/2) and pdy > -img:getHeight() and pdy < 0 then + a = 0.5 + end + love.graphics.setColor(1, 1, 1, a) + love.graphics.push() + love.graphics.translate(vx, vy) + love.graphics.draw(img, 0, 0, 0, 1, 1, lume.round(img:getWidth()/2), img:getHeight()) + love.graphics.pop() +end + + + +return bigRock diff --git a/entityDefs/bush.lua b/entityDefs/bush.lua new file mode 100644 index 0000000..bc5d568 --- /dev/null +++ b/entityDefs/bush.lua @@ -0,0 +1,49 @@ + +local base = require 'entityDefs._base' +local bush = { + server = base.server:new(), + client = base.client:new() +} + +for _, sc in ipairs{'server', 'client'} do + bush[sc].newDefaults = function() + local t = { + id = lume.uuid(), + x = 0, y = 0 + } + if sc == 'server' then + t.base = base.server + t.realm = serverRealm + elseif sc == 'client' then + t.base = base.client + t.realm = clientRealm + end + return t + end + + bush[sc].type = 'bush' +end + + + +function bush.client:draw() + local img = gfx.environment.bush + vx, vy = lume.round(self.x), lume.round(self.y) + local p = playerController.player + local px, py = lume.round(p.x), lume.round(p.y) + local pdx = px - vx + local pdy = py - vy + local a = 1 + if math.abs(px - vx) < lume.round(img:getWidth()/2) and pdy > -img:getHeight() and pdy < 0 then + a = 0.5 + end + love.graphics.setColor(1, 1, 1, a) + love.graphics.push() + love.graphics.translate(vx, vy) + love.graphics.draw(img, 0, 0, 0, 1, 1, lume.round(img:getWidth()/2), img:getHeight()) + love.graphics.pop() +end + + + +return bush diff --git a/entityDefs/godex.lua b/entityDefs/godex.lua index a321fd0..d26f62f 100644 --- a/entityDefs/godex.lua +++ b/entityDefs/godex.lua @@ -100,46 +100,7 @@ function godex.server:update(dt) end function godex.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -232,8 +193,7 @@ function godex.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/entityDefs/mudskipper.lua b/entityDefs/mudskipper.lua index e7d9a01..d4dc674 100644 --- a/entityDefs/mudskipper.lua +++ b/entityDefs/mudskipper.lua @@ -99,46 +99,7 @@ function mudskipper.server:update(dt) end function mudskipper.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -223,8 +184,7 @@ function mudskipper.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - w/2 and wmx < vx + w/2 diff --git a/entityDefs/mudskipperEvolved.lua b/entityDefs/mudskipperEvolved.lua index d390669..48a6727 100644 --- a/entityDefs/mudskipperEvolved.lua +++ b/entityDefs/mudskipperEvolved.lua @@ -99,46 +99,7 @@ function mudskipperEvolved.server:update(dt) end function mudskipperEvolved.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -223,8 +184,7 @@ function mudskipperEvolved.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - w/2 and wmx < vx + w/2 diff --git a/entityDefs/newMonster1.lua b/entityDefs/newMonster1.lua index 519b36d..3b3d1ee 100644 --- a/entityDefs/newMonster1.lua +++ b/entityDefs/newMonster1.lua @@ -100,46 +100,7 @@ function newMonster1.server:update(dt) end function newMonster1.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -222,8 +183,7 @@ function newMonster1.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/entityDefs/newMonster2.lua b/entityDefs/newMonster2.lua index 1c4e795..4c866fa 100644 --- a/entityDefs/newMonster2.lua +++ b/entityDefs/newMonster2.lua @@ -100,46 +100,7 @@ function newMonster2.server:update(dt) end function newMonster2.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -222,8 +183,7 @@ function newMonster2.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/entityDefs/player.lua b/entityDefs/player.lua index a162adf..5393456 100644 --- a/entityDefs/player.lua +++ b/entityDefs/player.lua @@ -267,11 +267,14 @@ end function player.client:damage(dmg) self.hp = self.hp - dmg if self.hp <= 0 then + sound.play('death') self.hp = self.hpMax local a = math.random()*2*math.pi local dist = math.random()*128 self.x, self.y = math.cos(a)*dist, -math.sin(a)*dist self.body:setPosition(self.x, self.y) + else + sound.play('hurt') end end diff --git a/entityDefs/slime.lua b/entityDefs/slime.lua index 3973a96..69a5f95 100644 --- a/entityDefs/slime.lua +++ b/entityDefs/slime.lua @@ -100,47 +100,7 @@ function slime.server:update(dt) end function slime.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - 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 + serverEnemyDamage(self, d, clientId) end @@ -227,8 +187,7 @@ function slime.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/entityDefs/smallRock.lua b/entityDefs/smallRock.lua new file mode 100644 index 0000000..19c41ee --- /dev/null +++ b/entityDefs/smallRock.lua @@ -0,0 +1,91 @@ + +local base = require 'entityDefs._base' +local smallRock = { + server = base.server:new(), + client = base.client:new() +} + +for _, sc in ipairs{'server', 'client'} do + smallRock[sc].newDefaults = function() + local t = { + id = lume.uuid(), + x = 0, y = 0 + } + if sc == 'server' then + t.base = base.server + t.realm = serverRealm + elseif sc == 'client' then + t.base = base.client + t.realm = clientRealm + end + return t + end + + smallRock[sc].spawn = function(self) + self.body = love.physics.newBody(self.realm.physics.world, self.x, self.y, 'static') + self.polys = { + {0.125, 0.05, 0.8, 0.05, 0.95, 0.175, 0.05, 0.175} + } + -- transform - todo: load from file already transformed + local img = gfx.environment.smallRock + for _, v in pairs(self.polys) do + for i2, v2 in pairs(v) do + if (i2-1) % 2 == 0 then -- x + v[i2] = v2*img:getWidth() - img:getWidth()/2 + else + v[i2] = v2*img:getWidth()*-1 + end + end + end + self.shapes = {} + self.fixtures = {} + for _, v in pairs(self.polys) do + local shape = love.physics.newPolygonShape(unpack(v)) + table.insert(self.shapes, shape) + local fixture = love.physics.newFixture(self.body, shape, 1) + table.insert(self.fixtures, fixture) + fixture:setUserData(self) + fixture:setCategory(4) + end + return self.base.spawn(self) + end + + smallRock[sc].destroy = function(self) + if self.fixtures then + for _, v in pairs(self.fixtures) do + if not v:isDestroyed() then v:destroy() end + end + end + if self.body and not self.body:isDestroyed() then + self.body:destroy() + end + self.base.destroy(self) + end + + smallRock[sc].type = 'smallRock' +end + + + +function smallRock.client:draw() + local img = gfx.environment.smallRock + local vx, vy = self.body:getPosition() + vx, vy = lume.round(vx), lume.round(vy) + local p = playerController.player + local px, py = lume.round(p.x), lume.round(p.y) + local pdx = px - vx + local pdy = py - vy + local a = 1 + if math.abs(px - vx) < lume.round(img:getWidth()/2) and pdy > -img:getHeight() and pdy < 0 then + a = 0.5 + end + love.graphics.setColor(1, 1, 1, a) + love.graphics.push() + love.graphics.translate(vx, vy) + love.graphics.draw(img, 0, 0, 0, 1, 1, lume.round(img:getWidth()/2), img:getHeight()) + love.graphics.pop() +end + + + +return smallRock diff --git a/entityDefs/sorcerer.lua b/entityDefs/sorcerer.lua index b1ce624..eaba7e7 100644 --- a/entityDefs/sorcerer.lua +++ b/entityDefs/sorcerer.lua @@ -101,46 +101,7 @@ function sorcerer.server:update(dt) end function sorcerer.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -226,8 +187,7 @@ function sorcerer.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/entityDefs/spoder.lua b/entityDefs/spoder.lua index 3e72a83..2bd7cef 100644 --- a/entityDefs/spoder.lua +++ b/entityDefs/spoder.lua @@ -100,46 +100,7 @@ function spoder.server:update(dt) end function spoder.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -225,8 +186,7 @@ function spoder.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/entityDefs/stingy.lua b/entityDefs/stingy.lua index 50251d3..d9e268b 100644 --- a/entityDefs/stingy.lua +++ b/entityDefs/stingy.lua @@ -100,46 +100,7 @@ function stingy.server:update(dt) end function stingy.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -225,8 +186,7 @@ function stingy.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/entityDefs/tree.lua b/entityDefs/tree.lua index 4f26de1..a1d6bea 100644 --- a/entityDefs/tree.lua +++ b/entityDefs/tree.lua @@ -23,14 +23,30 @@ for _, sc in ipairs{'server', 'client'} do tree[sc].spawn = function(self) self.body = love.physics.newBody(self.realm.physics.world, self.x, self.y, 'static') + self.polys = { + {0.75, 0.125, 0.475, 0.2, 0.25, 0.125, 0.475, 0.05} + } + -- transform - todo: load from file already transformed + local img = gfx.environment.tree + for _, v in pairs(self.polys) do + for i2, v2 in pairs(v) do + if (i2-1) % 2 == 0 then -- x + v[i2] = v2*img:getWidth() - img:getWidth()/2 + else + v[i2] = v2*img:getWidth()*-1 + end + end + end self.shapes = {} self.fixtures = {} - local shape = love.physics.newCircleShape(6) - table.insert(self.shapes, shape) - local fixture = love.physics.newFixture(self.body, shape, 1) - table.insert(self.fixtures, fixture) - fixture:setUserData(self) - fixture:setCategory(4) + for _, v in pairs(self.polys) do + local shape = love.physics.newPolygonShape(unpack(v)) + table.insert(self.shapes, shape) + local fixture = love.physics.newFixture(self.body, shape, 1) + table.insert(self.fixtures, fixture) + fixture:setUserData(self) + fixture:setCategory(4) + end return self.base.spawn(self) end @@ -52,19 +68,22 @@ end function tree.client:draw() - local _shader = love.graphics.getShader() - love.graphics.setColor(1, 1, 1) - love.graphics.setShader(shaders.outline) local img = gfx.environment.tree - shaders.outline:send('stepSize', {1/img:getWidth(), 1/img:getHeight()}) - shaders.outline:send('outlineColor', {0, 0, 0, 1}) - love.graphics.push() local vx, vy = self.body:getPosition() - love.graphics.translate(lume.round(vx), lume.round(vy)) - love.graphics.draw(img, 0, 0, 0, 1, 1, - lume.round(img:getWidth()/2 + 1), img:getHeight() - 6) + vx, vy = lume.round(vx), lume.round(vy) + local p = playerController.player + local px, py = lume.round(p.x), lume.round(p.y) + local pdx = px - vx + local pdy = py - vy + local a = 1 + if math.abs(px - vx) < lume.round(img:getWidth()/2) and pdy > -img:getHeight() and pdy < 0 then + a = 0.5 + end + love.graphics.setColor(1, 1, 1, a) + love.graphics.push() + love.graphics.translate(vx, vy) + love.graphics.draw(img, 0, 0, 0, 1, 1, lume.round(img:getWidth()/2), img:getHeight()) love.graphics.pop() - love.graphics.setShader(_shader) end diff --git a/entityDefs/zombie.lua b/entityDefs/zombie.lua index 5c8fe69..14a398d 100644 --- a/entityDefs/zombie.lua +++ b/entityDefs/zombie.lua @@ -100,46 +100,7 @@ function zombie.server:update(dt) end function zombie.server:damage(d, clientId) - self.hp = self.hp - d - if self.hp <= 0 and not self.destroyed then - server.addXP(clientId, math.random(3, 5)) - local bagItems = {} - local choices = { - none=50, shield=15, apple=20, - sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 - } - for _=1, 3 do - choice = lume.weightedchoice(choices) - if choice ~= 'none' then - local itemData = {imageId=choice} - if choice == 'sword0' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) - elseif choice =='sword1' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) - elseif choice =='sword2' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) - elseif choice =='sword3' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) - elseif choice =='sword4' then - itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) - end - local itemId = items.server.newItem(itemData) - table.insert(bagItems, itemId) - end - end - local numItems = #bagItems - if numItems ~= 0 then - local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} - lootBag.server:new{ - realm = serverRealm, - x = self.x, y = self.y, - items = bagItems, - type = type, - life = 30 - }:spawn() - end - self:destroy() - end + serverEnemyDamage(self, d, clientId) end @@ -225,8 +186,7 @@ function zombie.client:draw() love.graphics.pop() -- name, level - local mx, my = love.mouse.getPosition() - mx, my = window2game(mx, my) + local mx, my = window2game(love.mouse.getPosition()) mx, my = lume.round(mx), lume.round(my) local wmx, wmy = camera:screen2world(mx, my) if wmx > vx - img:getWidth()/2 and wmx < vx + img:getWidth()/2 diff --git a/gfx/environment/bigRock.png b/gfx/environment/bigRock.png new file mode 100644 index 0000000..7a0f1fe Binary files /dev/null and b/gfx/environment/bigRock.png differ diff --git a/gfx/environment/bush.png b/gfx/environment/bush.png new file mode 100644 index 0000000..1cdd7e3 Binary files /dev/null and b/gfx/environment/bush.png differ diff --git a/gfx/environment/smallRock.png b/gfx/environment/smallRock.png new file mode 100644 index 0000000..a0ace8f Binary files /dev/null and b/gfx/environment/smallRock.png differ diff --git a/gfx/environment/tree.png b/gfx/environment/tree.png index df19bdf..2670074 100644 Binary files a/gfx/environment/tree.png and b/gfx/environment/tree.png differ diff --git a/loadassets.lua b/loadassets.lua index 88d3d2d..8962251 100644 --- a/loadassets.lua +++ b/loadassets.lua @@ -52,8 +52,11 @@ gfx = { smoothTiles = love.graphics.newImage('gfx/tiles/smooth_tiles.png') }, environment = { + wall = love.graphics.newImage('gfx/environment/wall.png'), tree = love.graphics.newImage('gfx/environment/tree.png'), - wall = love.graphics.newImage('gfx/environment/wall.png') + bush = love.graphics.newImage('gfx/environment/bush.png'), + bigRock = love.graphics.newImage('gfx/environment/bigRock.png'), + smallRock = love.graphics.newImage('gfx/environment/smallRock.png') }, player = { walk = { @@ -256,3 +259,13 @@ end shaders.mapRender:send('smoothTiles', unpack(smoothTileImgs)) shaders.lifemana:send('lifemanaEmpty', gfx.hud.lifemanaEmpty) + +sfx = { + select = love.audio.newSource('sfx/Select.wav', 'static'), + select2 = love.audio.newSource('sfx/Select2.wav', 'static'), + death = love.audio.newSource('sfx/Death.wav', 'static'), + heal = love.audio.newSource('sfx/Heal2.wav', 'static'), + hurt = love.audio.newSource('sfx/Hurt.wav', 'static'), + scream = love.audio.newSource('sfx/Scream.wav', 'static'), + spider = love.audio.newSource('sfx/Spider.wav', 'static') +} diff --git a/main.lua b/main.lua index fcf98f9..902b129 100644 --- a/main.lua +++ b/main.lua @@ -11,6 +11,7 @@ Camera = require 'lib.camera' require 'utils' require 'loadassets' +require 'sound' require 'server' require 'client' require 'cursor' @@ -38,6 +39,54 @@ for i, v in ipairs{'water', 'sand', 'grass', 'rock', 'path', 'floor', 'wall', 'p tile2id[v] = i end +-- todo: enemy inheritance +function serverEnemyDamage(self, d, clientId) + self.hp = self.hp - d + if self.hp <= 0 and not self.destroyed then + sound.play('scream') + server.addXP(clientId, math.random(3, 5)) + local bagItems = {} + local choices = { + none=50, shield=15, apple=20, + sword0=3, sword1=3, sword2=3, sword3=3, sword4=3 + } + for _=1, 3 do + choice = lume.weightedchoice(choices) + if choice ~= 'none' then + local itemData = {imageId=choice} + if choice == 'sword0' then + itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+10)) + elseif choice =='sword1' then + itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+12)) + elseif choice =='sword2' then + itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+14)) + elseif choice =='sword3' then + itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+16)) + elseif choice =='sword4' then + itemData.atk = math.max(5, math.floor(love.math.randomNormal()*2+18)) + end + local itemId = items.server.newItem(itemData) + table.insert(bagItems, itemId) + end + end + local numItems = #bagItems + if numItems ~= 0 then + local type = lume.randomchoice{'lootBag', 'lootBag1', 'lootBagFuse'} + 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() + else + sound.play('spider') + end +end + function love.load() camera = Camera{ssx=gsx, ssy=gsy} gameState = 'menu' diff --git a/menu.lua b/menu.lua index 154dcfc..8741527 100644 --- a/menu.lua +++ b/menu.lua @@ -9,7 +9,7 @@ function menu.addButton(t) type = 'default', x = 480/2, y = 270/2 - -- action nil + -- active, action nil } for k, v in pairs(defaults) do if t[k] == nil then t[k] = v end @@ -32,6 +32,29 @@ function menu.addButton(t) return t end +function menu.addSlider(t) + local defaults = { + state = 'main', + text = 'Slider', + font = fonts.c17, + value = 0.5, + x = 480/2, + y = 270/2, + w = 80 + } + for k, v in pairs(defaults) do + if t[k] == nil then t[k] = v end + end + t.w = lume.round(t.w) + t.bw = t.w + t.bh = lume.round(t.font:getHeight() + 4) + t.bx = lume.round(t.x - t.bw/2) + t.by = lume.round(t.y - t.bh/2) + if not menu.sliders[t.state] then menu.sliders[t.state] = {} end + table.insert(menu.sliders[t.state], t) + return t +end + function menu.addInput(t) local defaults = { state = 'main', @@ -40,11 +63,12 @@ function menu.addInput(t) value = '', x = 480/2, y = 270/2, + w = 80 } for k, v in pairs(defaults) do if t[k] == nil then t[k] = v end end - t.w = lume.round(t.w or 80) + t.w = lume.round(t.w) t.bw = t.w t.bh = lume.round(t.font:getHeight() + 4) t.bx = lume.round(t.x - t.bw/2) @@ -77,9 +101,11 @@ menu.factoryDefaults = { resolution = 1, fullscreen = 1, vsync = false, - cursorLock = false + cursorLock = false, + masterVolume = 0.4 } +-- mutable defaults function menu.readDefaults() local path = love.filesystem.getRealDirectory('menuDefaults.json') .. '/menuDefaults.json' local file = io.open(path, 'rb') @@ -101,7 +127,8 @@ function menu.writeDefaults() resolution = menu.resolutionBtn.active, fullscreen = menu.fullscreenBtn.active, vsync = menu.vsyncBtn.active, - cursorLock = menu.cursorLockBtn.active + cursorLock = menu.cursorLockBtn.active, + masterVolume = menu.masterVolumeSlider.value } love.filesystem.write('menuDefaults.json', content) end @@ -109,10 +136,10 @@ end function menu.applyOptions() local w, h, flags = love.window.getMode() local vsyncOn = flags.vsync ~= 0 - flags.vsync = menu.defaults.vsync and 1 or 0 + flags.vsync = menu.vsyncBtn.active and 1 or 0 local fullscreen, fstype = love.window.getFullscreen() - local newResolution = menu.resolutionBtn.items[menu.defaults.resolution] + local newResolution = menu.resolutionBtn.items[menu.resolutionBtn.active] if not (fullscreen and fstype == 'desktop') and newResolution ~= string.format('%sx%s', w, h) then w, h = newResolution:match('(%d+)x(%d+)') @@ -126,7 +153,7 @@ function menu.applyOptions() end local currentWindowType = love.window.getFullscreen() - local newWindowType = menu.fullscreenBtn.items[menu.defaults.fullscreen] + local newWindowType = menu.fullscreenBtn.items[menu.fullscreenBtn.active] if newWindowType == 'Windowed' and currentWindowType ~= 'Windowed' then love.window.setFullscreen(false) @@ -143,11 +170,14 @@ function menu.applyOptions() w, h = love.graphics.getDimensions() love.resize(w, h) end + + love.audio.setVolume(menu.masterVolumeSlider.value) end function menu.load() menu.state = 'main' menu.buttons = {} + menu.sliders = {} menu.inputs = {} menu.infos = {} menu.activeInput = nil @@ -160,20 +190,25 @@ function menu.load() menu.readDefaults() end + -- main menu local exitY = 220 local h = 40 menu.addButton{text='Play', y=exitY - h*2.5, action = function() + sound.play('select') menu.state = 'play' end} menu.addButton{text='Options', y=exitY - h*1.5, action=function() - menu.state = 'options' + sound.play('select') + menu.state = 'options_video' end} menu.addButton{text='Exit', y=exitY, action = function() love.event.quit() end} + -- play menu menu.nameInput = menu.addInput{state='play', text='Player Name', value=menu.defaults.name, x=gsx/2 - 70, y=exitY - h*2} menu.addButton{state='play', text='Singleplayer', x=gsx/2 - 70, y=exitY - h*1, action=function() + sound.play('select') chat.log = {} server.start(nil, true) client.connect('127.0.0.1', server.nutServer.port) @@ -183,6 +218,7 @@ function menu.load() menu.ipInput = menu.addInput{state='play', text='IP', value=menu.defaults.ip, x=gsx/2 + 70, y=exitY - h*3} menu.portInput = menu.addInput{state='play', text='Port', value=menu.defaults.port, x=gsx/2 + 70, y=exitY - h*2} menu.addButton{state='play', text='Host', x=gsx/2 + 70 - 25, y=exitY - h*1, action=function() + sound.play('select') chat.log = {} -- todo: notify if not open or other err -- remove whitespace @@ -193,6 +229,7 @@ function menu.load() menu.connectInfo.text = 'Starting Game' end} menu.addButton{state='play', text='Join', x=gsx/2 + 70 + 25, y=exitY - h*1, action=function() + sound.play('select') chat.log = {} local ip = menu.ipInput.value:gsub("%s+", "") local port = menu.portInput.value:gsub("%s+", "") @@ -201,11 +238,14 @@ function menu.load() menu.connectInfo.text = 'Starting Game' end} menu.addButton{state='play', text='Back', y=exitY, action=function() + sound.play('select2') menu.state = 'main' end} + -- connecting screen menu.connectInfo = menu.addInfo{state='connect', text='[connection info]', y=gsy/2} menu.addButton{state='connect', text='Cancel', y=exitY, action=function() + sound.play('select2') menu.state = 'play' if server.running then server.close() @@ -215,9 +255,19 @@ function menu.load() end end} - menu.resolutionBtn = menu.addButton{state='options', text='Resolution', y=exitY - h*4, + -- options menu - video + menu.addButton{state='options_video', text='Video', x=40, y=20, action=function() + sound.play('select') + menu.state = 'options_video' + end} + menu.addButton{state='options_video', text='Audio', x=100, y=20, action=function() + sound.play('select') + menu.state = 'options_audio' + end} + menu.resolutionBtn = menu.addButton{state='options_video', text='Resolution', y=exitY - h*4, type='cycle', items={'960x540', '1440x810', '1920x1080'}, active=menu.defaults.resolution, action=function(v) + sound.play('select') local fullscreen, fstype = love.window.getFullscreen() if not fullscreen then local w, h, flags = love.window.getMode() @@ -251,9 +301,10 @@ function menu.load() end text.print(txt, lume.round(v.x - v.font:getWidth(txt)/2), lume.round(v.y - v.font:getHeight()/2)) end} - menu.fullscreenBtn = menu.addButton{state='options', text='Fullscreen', y=exitY - h*3, + menu.fullscreenBtn = menu.addButton{state='options_video', text='Fullscreen', y=exitY - h*3, type='cycle', items={'Windowed', 'Borderless Fullscreen Windowed', 'Fullscreen'}, active=menu.defaults.fullscreen, action=function(v) + sound.play('select') if v == 'Windowed' then local w, h, flags = love.window.getMode() local rb = menu.resolutionBtn @@ -280,21 +331,53 @@ function menu.load() love.resize(w, h) end end} - menu.vsyncBtn = menu.addButton{state='options', text='Vsync', y=exitY - h*2.2, type='toggle', + menu.vsyncBtn = menu.addButton{state='options_video', text='Vsync', y=exitY - h*2.2, type='toggle', active=menu.defaults.vsync, action=function(v) + sound.play('select') local w, h, flags = love.window.getMode() flags.vsync = v and 1 or 0 love.window.setMode(w, h, flags) end} - menu.cursorLockBtn = menu.addButton{state='options', text='Cursor Lock', y=exitY - h*1.4, type='toggle', active=menu.defaults.cursorLock} - menu.addButton{state='options', text='Back', y=exitY, action=function() + menu.cursorLockBtn = menu.addButton{state='options_video', text='Cursor Lock', y=exitY - h*1.4, type='toggle', + active=menu.defaults.cursorLock, action=function(v) + sound.play('select') + end} + menu.addButton{state='options_video', text='Back', y=exitY, action=function() + sound.play('select2') menu.state = 'main' end} - menu.addButton{state='options', text='Load Defaults', x=400, y=exitY, action=function() + menu.addButton{state='options_video', text='Load Defaults', x=400, y=exitY, action=function() + sound.play('select') local content = json.encode(menu.factoryDefaults) love.filesystem.write('menuDefaults.json', content) menu.load() - menu.state = 'options' + menu.state = 'options_video' + end} + + -- options menu - audio + menu.addButton{state='options_audio', text='Video', x=40, y=20, action=function() + sound.play('select') + menu.state = 'options_video' + end} + menu.addButton{state='options_audio', text='Audio', x=100, y=20, action=function() + sound.play('select') + menu.state = 'options_audio' + end} + menu.masterVolumeSlider = menu.addSlider{state='options_audio', text='Master Volume', + w=120, value=menu.defaults.masterVolume, action=function(v) + sound.play('select') + love.audio.setVolume(v) + end} + menu.addButton{state='options_audio', text='Back', y=exitY, action=function() + sound.play('select2') + menu.state = 'main' + end} + menu.addButton{state='options_audio', text='Load Defaults', x=400, y=exitY, action=function() + sound.play('select') + local content = json.encode(menu.factoryDefaults) + love.filesystem.write('menuDefaults.json', content) + menu.load() + menu.state = 'options_audio' end} menu.applyOptions() @@ -302,6 +385,19 @@ end function menu.update(dt) menu.logoAnimTimer = menu.logoAnimTimer + dt + + local mx, my = window2game(love.mouse.getPosition()) + mx, my = lume.round(mx), lume.round(my) + if love.mouse.isDown(1) then + for _, v in pairs(menu.sliders[menu.state] or {}) do + if mx > v.bx and mx < v.bx + v.bw and my > v.by and my < v.by + v.bh then + v.value = (mx - v.bx)/v.bw + if v.action then v.action(v.value) end + uiMouseDown = true + return + end + end + end end function menu.mousepressed(mx, my, btn) @@ -356,7 +452,7 @@ function menu.keypressed(k, scancode, isrepeat) menu.state = 'play' menu.activeInput = nil end - elseif menu.state == 'options' then + elseif menu.state == 'options_video' or menu.state == 'options_audio' then if k == 'escape' and not isrepeat then menu.state = 'main' menu.activeInput = nil @@ -413,6 +509,27 @@ function menu.draw() text.print(txt, lume.round(v.x - v.font:getWidth(txt)/2), lume.round(v.y - v.font:getHeight()/2)) end end + for _, v in pairs(menu.sliders[menu.state] or {}) do + if v.draw then + v.draw(v, mx, my) + else + if mx > v.bx and mx < v.bx + v.bw and my > v.by and my < v.by + v.bh then + love.graphics.setColor(0.3, 0.3, 0.3) + love.graphics.rectangle('fill', v.bx, v.by, v.bw, v.bh) + love.graphics.setColor(0.4, 0.4, 0.4) + love.graphics.rectangle('fill', v.bx, v.by, v.bw*v.value, v.bh) + else + love.graphics.setColor(0.4, 0.4, 0.4) + love.graphics.rectangle('fill', v.bx, v.by, v.bw, v.bh) + love.graphics.setColor(0.5, 0.5, 0.5) + love.graphics.rectangle('fill', v.bx, v.by, v.bw*v.value, v.bh) + end + local txt = v.text + love.graphics.setFont(v.font) + love.graphics.setColor(0.8, 0.8, 0.8) + text.print(v.text, lume.round(v.x - v.font:getWidth(v.text)/2), lume.round(v.by - v.font:getHeight())) + end + end for _, v in pairs(menu.inputs[menu.state] or {}) do if v.draw then v.draw(v, mx, my) diff --git a/sfx/Death.wav b/sfx/Death.wav new file mode 100644 index 0000000..312116e Binary files /dev/null and b/sfx/Death.wav differ diff --git a/sfx/Heal2.wav b/sfx/Heal2.wav new file mode 100644 index 0000000..86b7ecd Binary files /dev/null and b/sfx/Heal2.wav differ diff --git a/sfx/Hurt.wav b/sfx/Hurt.wav new file mode 100644 index 0000000..61c53a8 Binary files /dev/null and b/sfx/Hurt.wav differ diff --git a/sfx/Scream.wav b/sfx/Scream.wav new file mode 100644 index 0000000..5befad1 Binary files /dev/null and b/sfx/Scream.wav differ diff --git a/sfx/Select.wav b/sfx/Select.wav new file mode 100644 index 0000000..6d9891c Binary files /dev/null and b/sfx/Select.wav differ diff --git a/sfx/Select2.wav b/sfx/Select2.wav new file mode 100644 index 0000000..6389394 Binary files /dev/null and b/sfx/Select2.wav differ diff --git a/sfx/Spider.wav b/sfx/Spider.wav new file mode 100644 index 0000000..fe081dc Binary files /dev/null and b/sfx/Spider.wav differ diff --git a/sound.lua b/sound.lua new file mode 100644 index 0000000..c1f2e04 --- /dev/null +++ b/sound.lua @@ -0,0 +1,6 @@ + +sound = {} + +function sound.play(name) + sfx[name]:clone():play() +end