0,]']±>Sï}WÿÍu§ðp8·d%qy)6o;]ÉA¦ösï÷ôôô”°ÂVl†344#îîîïð@0##àÀÀ0À ,B<#L"Ã$0C!ÀD"LA44D"21"""""""""ÝÐÐàô@Ð=ô@ó0íîCðî ðßÿïàïþÿðððððÿïðÿÐàÀ"CÁÀ2<D1C14ÃAÁ01Á@A 4ÃÃ44$!!ÌC"!D#"4"!"#""""""""""""""""""""""""""""""""""íÐþàÐààÐàÐàà4ôÐ@íÿ4 OÐÿ0î? ÿÿðÿÿâÿ ã?ó2ðS5òevevòs'/ò Þîÿ ðÞïðï $""À !«š !""""""""!"""ÂD#!ÁDL403"""!""""!!!ÁLÁ4A3?DDñD3ñÿUPfvwÐíàÿíàíÐà Ðà àðÿðððÿÿîðÿððððàþßàÿßÐðíÐàÐàÐ àÞ ÿîÐïàðÐà àÐàðþðÿLÀD3þÿðÿÿàðÿïÿÿÿðÿðÿÿïîÿÿÿîàþîÿßþðíÿðîîðÝíþþîïïðïïà ðîÝÐíàþ@?@ðîîàîïðð<ðôððÿÿÿÿîîîÿÿÿîïðàîàîîîàÿÿÿÿÿÿÿÿïðîîÿÿïîðÿíýþïþÿîþÿþàÿîÿÿ‹ ^    `g`eo`fn ` f o biofohokonooooooooo ÿèÒÖÆ¢¢Œ(6VhjyC    @@@3Äñ<ðððððððððððððððððððððððððððððàîàîîàîîàîîîîîîðîîîîîîîîîðîàîîîîîîîîîîðîîîîîàîîîîðàîîîîàîîîîððððððððððfffffffffffffffffðffffffffffffffffföðf`fffffffffff`fföðff````ff`ffföðfff``f```````öðff`f`f````````öðff`ff`f`f`````öðffffffffffffffföðffffffffffffffffföðffffffffffffffffföððÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿððððððððððð  ðÀ  @ð@#ÔMÝ0ð@4DMÝÔ#ðDCCC3#ðî>44#ðîîîïðîðàðàî ðàðßÿïðôàïþÿð@ðððððÐ=ôððÝ@óðÐ0ÿïðÐíîCððÿÐðí4ôÿâ ðÐþàÐ@ÿ ãÞðÐàíÿ4?ó2ðîðà OS5òÿýÐÐÿ0evðÞàÐî?evòïà ÿÿs'ðàðÿ/òðïÐàðÿððÐðíàíÿððÐàÿÿððÐïÐ ÿÿíðýàÞðÿàíÿÿàÿ ÿîðÿÿÐà îþßÐïÿÿÐà ðÿïÿßàððÿÐà àþÿïþîðîðÿÿà ÐðÿÿïîÿßÝïÿÿÄ2 àðÿÿïþðííïðÿB<"ðþÿÿÿÿðþððÿÿ 4ðÿÿðÿðþïÿÿ""1D ÿÿïÐðÿ ""!CÄLôÿïîà íðÿÿ"""!D!CÀ ÀD3ÿÿÿîîðîÝàþÿÿ!"""!#"$ÂO?ðÿÿÿÿîîîÿÿÿíýþðÿ""""!<<4" ÿ@ðÿÿÿîàîÿïïþÿð!"""!BCCÃA# ÿîîïàîîîðîþÿ""""14Dÿàîïðàÿÿÿîîþ!!""""4ÌCÿððÿÿÿïîàÿ"""""!2D<ÿ<ððÿî!!"""""Cÿôÿ""""""!21ôÿÿðÿ!"""""""CAðÿÿÿ"""""""BAÿÿÿDDó!!""""""""ðÿÿ03D"""""° ðÿÿÿÿ!!!!!!!!©ºÿÿÿÄ2D, ðÿÿPUC!!!1Dðÿÿÿgf3CÄÿÿÿpw 3ðÿÿðÿÿÿÿÿÿðÿÿðÿÿÿÿÿÿÿÿÿðA-- title: inventory -- author: Xevin -- desc: Game about inventory and loot -- script: lua -- input: mouse DEBUG = true TILE_WIDTH = 8 TILE_HEIGHT = 8 CURSOR_IDX = 257 -- COLORS -- SWEETIE-16 PALETTE BLACK = 0 MAGENTA = 1 RED = 2 ORANGE = 3 YELLOW = 4 LIME = 5 GREEN = 6 DARKGREEN = 7 DARKBLUE = 8 BLUE = 9 LIGHTBLUE = 10 CIAN = 11 WHITE = 12 LIGHTGRAY = 13 GRAY = 14 DARKGRAY = 15 TILES = { INVENTORY_CELL = 1, ALLOW_CELL = 4, DEPRECATED_CELL = 5, SKELETON = 272, SKELETON_BAG = 276, SKELETON_LEGS = 340 } ROUND_TIME = 60 DOOR_START_TIME = ROUND_TIME / 3 DEFAULT_ITEM_PRICE = 1000 local BASE_BALANCE = -6000 -- BUTTONS BTN = { A = 4, B = 5 } -- SCREEN SCR = { W = 240, H = 136 } SimpleFSM = { -- SimpleFSM v1.0.1 new = function(self, stateList) local props = { state = '', stateList = stateList or {} } self.__index = self return setmetatable(props, self) end, isStateExists = function(self, stateName) for index in pairs(self.stateList) do if index == stateName then return true end end return false end, setState = function(self, state) if state and self:isStateExists(state) then self.state = state end end } Item = { new = function(self, x, y, w, h, spr, mask, id, price) local props = { x = x, y = y, w = w, h = h, spr = spr, mask = mask or {}, colorKey = BLACK, id = id, price = price } props.width = w * TILE_WIDTH props.height = h * TILE_HEIGHT self.__index = self return setmetatable(props, self) end, getX2 = function(self) return self.x + (self.w * TILE_WIDTH) end, getY2 = function(self) return self.y + (self.h * TILE_HEIGHT) end, getRoundedX = function(self) return roundPosition(self.x) end, getRoundedY = function(self) return roundPosition(self.y) end, isCollidedWithPoint = function(self, x, y) return isCollidedRectWithPoint(self.x, self.y, self:getX2(), self:getY2(), x, y) end, getTiles = function(self) local x = self:getRoundedX() local y = self:getRoundedY() local tiles = {} for i, v in ipairs(self.mask) do if v > 0 then local col = idx2col(i, self.w) local row = idx2row(i, self.w) local deltaX = (col - 1) * TILE_WIDTH local deltaY = (row - 1) * TILE_HEIGHT local newHighlight = { x = x + deltaX, y = y + deltaY } table.insert(tiles, newHighlight) end end return tiles end, isInsideInventory = function(self) return allTrue(self:getTiles(), function(item) local mx = item.x // TILE_WIDTH local my = item.y // TILE_HEIGHT local currentTile = mget(mx, my) return currentTile == TILES.INVENTORY_CELL end) end, isTilesIntersect = function(self, tileList) for i,tile in ipairs(self:getTiles()) do for j,tile2 in ipairs(tileList) do local a = { x=tile.x, y=tile.y, w=TILE_WIDTH-1, h=TILE_HEIGHT-1 } local b = { x=tile2.x, y=tile2.y, w=TILE_WIDTH-1, h=TILE_HEIGHT-1 } if isIntersectRects(a, b) then return true end end end return false end, isIntersectWithItems = function(self, itemList) for i,v in ipairs(itemList) do if v.id ~= self.id then local r1 = { x=self:getRoundedX(), y=self:getRoundedY(), w=self.width-1, h=self.height-1 } local r2 = { x=v:getRoundedX(), y=v:getRoundedY(), w=v.width-1, h=v.height-1 } local isIntersect = isIntersectRects(r1, r2) if isIntersect then if self:isTilesIntersect(v:getTiles()) then return true end end end end return false end, isFullyOutsideInventory = function(self) return allTrue(self:getTiles(), function(item) local mx = item.x // TILE_WIDTH local my = item.y // TILE_HEIGHT local currentTile = mget(mx, my) return currentTile ~= TILES.INVENTORY_CELL end) end, drawDropShadow = function(self, itemList) local isRightDropPlace = self:isInsideInventory() or self:isFullyOutsideInventory() for i, cell in ipairs(self:getTiles()) do if isRightDropPlace and not self:isIntersectWithItems(itemList) then spr(TILES.ALLOW_CELL, cell.x, cell.y, BLACK) else spr(TILES.DEPRECATED_CELL, cell.x, cell.y, BLACK) end end end } ItemData = { { name = 'mask', spr = 16, w = 2, h = 2, colorKey = 1, price = 500, mask = { 1, 1, 1, 1 } }, { name = 'crown', spr = 18, w = 3, h = 2, mask = { 0, 1, 0, 1, 1, 1 } }, { name = 'bird', spr = 50, w = 3, h = 2, mask = { 1, 1, 1, 0, 1, 0 } }, { name = 'the pendant', spr = 21, w = 2, h = 3, mask = { 0, 1, 0, 1, 1, 1 } }, { name = 'the scepter', spr = 23, w = 1, h = 4, price = 500, mask = { 1, 1, 1, 1 } }, { name = 'statuette', spr = 24, w = 3, h = 2, mask = { 0, 0, 1, 1, 1, 1 } }, --{ -- name = 'amphora', -- spr = 48, -- w = 2, -- h = 2, -- price = 500, -- mask = { -- 1, 1, -- 1, 1 -- } --}, { name = 'crown square', spr = 80, w = 2, h = 2, price = 500, mask = { 1, 1, 1, 1 } }, { name = 'ritual horn', spr = 27, w = 3, h = 2, mask = { 1, 0, 0, 1, 1, 1 } }, { name = 'scimitar', spr = 30, w = 2, h = 3, mask = { 0, 1, 1, 1, 1, 0 } }, { name = 'snake', spr = 69, w = 2, h = 3, mask = { 1, 0, 1, 1, 0, 1 } }, } Items = { new = function(self, itemIndexes, parent) local props = { pressed = false, click = false, pressedMode = false, clickSession = { x = nil, y = nil }, list = {}, itemsIndexes = itemIndexes or {} } self.__index = self self.parent = parent return setmetatable(props, self) end, init = function(self) -- generate list of random items for i, v in pairs(self.itemsIndexes) do local newItem = Item:new( roundPosition(math.random(0, 220)), roundPosition(math.random(80, 124)), ItemData[v].w, ItemData[v].h, ItemData[v].spr, ItemData[v].mask, i, ItemData[v].price or DEFAULT_ITEM_PRICEE ) newItem.colorKey = ItemData[v].colorKey or BLACK newItem.dragMode = false newItem.name = ItemData[v].name table.insert(self.list, newItem) end end, findClickedItem = function(self, mx, my) for i, v in pairs(self.list) do if v:isCollidedWithPoint(getCursorX(mx), my) then v.dragMode = true v.prevX = v.x v.prevY = v.y v.deltaX = mx - v.x v.deltaY = my - v.y sfx(0) table.remove(self.list, i) table.insert(self.list, v) break end end end, dropItem = function(self) for i, v in pairs(self.list) do if v.dragMode then v.dragMode = false local isAllowDropHere = v:isInsideInventory() or v:isFullyOutsideInventory() if isAllowDropHere and not v:isIntersectWithItems(self.list) then v.x = v:getRoundedX() v.y = v:getRoundedY() self.parent:updateScore() sfx(1) else v.x = v.prevX v.y = v.prevY sfx(1) end end end end, update = function(self, parent) for i, item in pairs(self.list) do if item.dragMode then local mx, my = mouse() item.x = mx - item.deltaX self.list[i].y = my - item.deltaY item:drawDropShadow(self.list) end spr( item.spr, item.x, item.y, item.colorKey, 1, 0, 0, item.w, item.h ) end end, input = function(self, parent) local mx, my, mPrimaryBtn = mouse() if mPrimaryBtn and self.click then self.pressedMode = true end if mPrimaryBtn and not self.pressedMode then self.click = true self:findClickedItem(mx, my) elseif not mPrimaryBtn and not self.pressedMode then self.click = false elseif mPrimaryBtn and self.pressedMode then self.click = false else self.pressedMode = false self:dropItem() end if #self.list > 0 then end end, getInventoryItems = function (self) local items = {} for i,v in ipairs(self.list) do if v:isInsideInventory() then table.insert(items, v) end end return items end, getInventoryPrice = function(self) local sum = 0 for i,v in ipairs(self:getInventoryItems()) do local price = v.price or DEFAULT_ITEM_PRICE sum = sum + price end return sum end } theDoor = { new = function(self, x, y, w, h, isOpened) local props = { x = x, y = y, w = w, h = h, value = isOpened and 0 or 1 } self.__index = self return setmetatable(props, self) end, setValue = function(self, value) if value > 1 then self.value = 1 elseif value < 0 then self.value = 0 else self.value = value end end, draw = function(self) clip(self.x, self.y, self.w, self.h) local a_x = self.x local a_y = self.y local a_w = self.w local a_h = self.h + 1 local b_x = a_x + 2 local b_w = a_w - 4 local b_h = a_h - 4 local openedDoorY = self.y - b_h local closedDoorY = a_y + 2 local doorDeltaY = (closedDoorY - openedDoorY) * self.value local b_y = openedDoorY + doorDeltaY rectb(b_x, b_y, b_w, b_h, LIGHTGRAY) rectb(a_x, a_y, a_w, a_h, DARKGRAY) clip() end } INTRO_SCN = { init = function(self, parent) self.parent = parent end, drawInterface = function(self) local buttonY = 100 function _drawStartButton(x, y) return drawButton("Start game", x, y, function() self.parent:setScene('run') end) end local buttonW = _drawStartButton(-100, 0) print('use pointer', 90, 130, DARKGRAY) _drawStartButton(screenXCenter(buttonW), buttonY) end, update = function(self, game) cls() local mx, my, mleft = mouse() if mleft then setPressedCursor() else setDefaultCursor() end self:drawInterface() end } RUN_SCN = { init = function(self, parent) cls(0) setDefaultCursor() local itemIndexes = {} local itemCount = 16 for i = 1, itemCount do table.insert(itemIndexes, math.random(1, #ItemData)) end self.items = Items:new(itemIndexes, self) self.items:init(self) self.timer = ROUND_TIME self.timeStamp = time() / 1000 self.door = theDoor:new(10, 0, 60, 60, true) self.parent = parent self.LEVEL_PRICE = BASE_BALANCE self.parent:setScore(self.parent:getScore() + self.LEVEL_PRICE) self.LEVEL_PRICE = self.parent:getScore() end, updateScore = function(self, count) local itemPrice = self.items:getInventoryPrice() self.parent:setScore(itemPrice + self.LEVEL_PRICE) end, drawScore = function(self) local color = RED if self.parent:getScore() == 0 then color = YELLOW elseif self:isBalancePositive() then color = GREEN end local money = math.abs(self.parent:getScore()) local str = (self:isBalancePositive() and '$' or '-$') .. money print('Balance: ' .. str, 80, 0, color) end, drawInterface = function(self) map(0) self:drawScore() if self:isUnlockedDoor() then drawButton('Escape!', 12, 50, function() self.parent:setScene('win') end) end end, drawExit = function(self) self.door:draw() end, isBalancePositive = function(self) return self.parent:getScore() >= 0 end, isUnlockedDoor = function(self) return self:isBalancePositive() end, update = function(self) cls(0) local mx, my, mleft = mouse() self:drawInterface() self:drawExit() local nowTime = time() / 1000 if self.timer < 1 then self.parent:setScene('gameOver') else self.timer = ROUND_TIME - (nowTime - self.timeStamp) local oneTimerPercent = DOOR_START_TIME / 100 local timerPercent = self.timer / oneTimerPercent local percents = (100 - timerPercent) / 100 self.door:setValue(percents) end if mleft then setPressedCursor() else setDefaultCursor() end self.items:input(self) self.items:update(self) --showMousePos(0,0, RED) print(math.floor(self.timer), 220, 0, 2) end } DOOR_SCN = { init = function(self, parent) self.parent = parent self.door = theDoor:new(10, 20, 50, 60, false) end, update = function (self) cls() if (btn(0)) then trace('btn 0') self.door:setValue(self.door.value - 0.01) end if (btn(1)) then self.door:setValue(self.door.value + 0.01) end self.door:draw() print(self.door.value, 200, 100, WHITE) end } GAMEOVER_SCN = { init = function(self, parent) self.parent = parent end, drawInterface = function(self) function tryAgain () self.parent:setScene('run') end drawButton('Try again', 148, 42, tryAgain) end, drawBackground = function (self, x, y) local wallColor = DARKGRAY function ln(x1, y1, x2, y2) line(x1, y1, x2, y2, wallColor) end local x1 = 77 local y1 = 0 local y2 = 90 -- walls ln(x1, y1, x1, y2) ln(x1, y2+1, 0, y2+40) ln(x1, y2, 240, 125) -- skeleton local skelX = 50 local skelY = 64 spr(TILES.SKELETON, skelX, skelY, 0, 1, 0, 0, 4, 6) spr(TILES.SKELETON_LEGS, skelX+32, skelY+32, 0, 1, 0, 0, 2, 2) -- bag local isFlip = 1 spr(TILES.SKELETON_BAG, skelX+92, skelY+32, 0, 1, isFlip, 0, 4, 3) end, update = function(self, game) local mx, my, mleft = mouse() cls(0) print('GAME OVER', 140, 28, GRAY) if mleft then setPressedCursor() else setDefaultCursor() end self:drawInterface(game) self:drawBackground() end } WIN_SCN = { init = function(self, parent) self.parent = parent end, update = function(self) cls() print('You win!', 100, 70, GREEN) print('BALANCE: $'.. self.parent:getScore(), 80, 0, BLUE) drawButton('next', 110, 90, function() self.parent:setScene('run') end) end } function getCursorX(x) return x + 4 end function booleanToNumber(value) return value and 1 or 0 end function setDefaultCursor() poke(0x3FFB, CURSOR_IDX) end function setPressedCursor() poke(0x3FFB, CURSOR_IDX + 1) end function roundPosition(x) local div = x % TILE_WIDTH local cleanX = x - div local delta = div > 4 and TILE_WIDTH or 0 return cleanX + delta end function isCollidedRectWithPoint(x1, y1, x2, y2, px, py) -- x1, x2, y1, y2 is rectangle -- px and py is x,y of some point if px >= x1 and px < x2 and py >= y1 and py < y2 then return true end return false end function isIntersectRects(a, b) local a_x2 = a.x + a.w local a_y2 = a.y + a.h local b_x2 = b.x + b.w local b_y2 = b.y + b.h -- Y not intersects if a_y2 < b.y or b_y2 < a.y then return false end -- X not intersects if a_x2 < b.x or b_x2 < a.x then return false end return true end function screenXCenter(w) -- in: w - width of rectangle -- out: x coordinate center of screen local halfWidth = w / 2 local SCREEN_WIDTH = 240 return (SCREEN_WIDTH / 2) - halfWidth end function allTrue(list, func) for i, v in ipairs(list) do if not func(v) then return false end end return true end function allEqual(list, func) local acc = func(list[1]) for i, v in ipairs(list) do if acc ~= func(v) then return false end end return true end function drawButton(text, x, y, func) local activeBGColor = BLACK local inactiveBGColor = GREEN local _mx, my, mleft = mouse() local mx = getCursorX(_mx) local h = 10 local color local bcolor local drawText = function(str, tx, ty, strColor) local smallFont = true return print(str, tx+2, ty+2, strColor, false, 1, smallFont) end local textWidth = drawText(text, x, y, bcolor) local w = textWidth + 3 if isCollidedRectWithPoint(x,y,x+w, y+h, mx, my) then color = activeBGColor bcolor = inactiveBGColor else color = inactiveBGColor bcolor = activeBGColor end if mleft and isCollidedRectWithPoint(x,y,x+w, y+h, mx, my) then func() end -- shadow rectb(x+1, y+1, w, h, DARKGRAY) rect(x, y, w, h, color) rectb(x, y, w, h, inactiveBGColor) drawText(text, x, y, bcolor) return w end function showMousePos (x, y, color) local mx, my = mouse() print('mx:'..mx..' my:'..my, x, y, color) end function idx2col(idx, colCount) -- convert index to column return ((idx - 1) % colCount) + 1 -- -1 and +1 for index correction end function idx2row(idx, colCount) return ((idx - idx2col(idx, colCount)) / colCount) + 1 -- +1 is lua index correction end function createGame () -- just wrapper local GAME = SimpleFSM:new({ intro = INTRO_SCN, run = RUN_SCN, gameOver = GAMEOVER_SCN, win = WIN_SCN, door = DOOR_SCN }) GAME.score = 0 function GAME.setScore(self, score) self.score = score end function GAME.getScore(self) return self.score end function GAME.setScene(self, sceneName) self:setState(sceneName) self.stateList[self.state]:init(self) end function GAME.update(self) self.stateList[self.state]:update(self) end --GAME:setScene('intro') GAME:setScene('run') --GAME:setScene('gameOver') --GAME:setScene('door') return GAME end GAME = createGame() function TIC() GAME:update() end