From ea0681b60d7de8bd3d9b0f98857a96e980397ef2 Mon Sep 17 00:00:00 2001 From: assada Date: Fri, 31 May 2024 13:19:42 +0300 Subject: [PATCH] some shit --- source/balebaSprite.lua | 58 +++ source/dangerSprite.lua | 27 ++ source/level.lua | 22 + source/lib/pdParticles.lua | 626 ++++++++++++++++++++++++++ source/main.lua | 4 + source/player.lua | 54 ++- source/sprites/baleba-table-64-64.png | Bin 0 -> 1510 bytes source/sprites/danger-table-64-64.png | Bin 0 -> 1039 bytes source/sprites/tank.png | Bin 0 -> 5522 bytes 9 files changed, 779 insertions(+), 12 deletions(-) create mode 100644 source/balebaSprite.lua create mode 100644 source/dangerSprite.lua create mode 100644 source/lib/pdParticles.lua create mode 100644 source/sprites/baleba-table-64-64.png create mode 100644 source/sprites/danger-table-64-64.png create mode 100644 source/sprites/tank.png diff --git a/source/balebaSprite.lua b/source/balebaSprite.lua new file mode 100644 index 0000000..ba17e57 --- /dev/null +++ b/source/balebaSprite.lua @@ -0,0 +1,58 @@ +local pd = playdate +local gfx = playdate.graphics + +class('Baleba').extends(AnimatedSprite) + +function randomFloat(lower, greater) + return lower + math.random() * (greater - lower); +end + +function Baleba:init(x, y, player) + local balebaImageTable = gfx.imagetable.new("sprites/baleba") + Baleba.super.init(self, balebaImageTable) + + self:moveTo(x, y) + + self:addState("run", 1,4, {tickStep = 2}) + + self:setDefaultState("run") + + self:playAnimation() + + self:setZIndex(11) + self:setCollideRect(3, 25, 40, 15) + + self:setTag(154) + + self.standrate = randomFloat(0.3, 2) + self.xVelocity = Baleba.standrate + self.player = player + + + self.danger = Danger(366, y, self) + self.danger:add() +end + +function Baleba:update() + self:updateAnimation() + if self.player.isMovingRight() == false then + self.xVelocity = self.standrate + else + self.xVelocity = 2 + self.standrate + end + + if self.x < 430 and self.danger ~= nil then + self.danger:remove() + self.danger = nil + elseif self.danger == nil and self.x > 430 then + self.danger = Danger(366, self.y, self) + self.danger:add() + end + + if self.x < 0 then + self.standrate = randomFloat(0.5, 6) + self:moveTo(math.random(450, 600), math.random(50, 200)) + end + + self:moveTo(self.x-self.xVelocity, self.y) +end diff --git a/source/dangerSprite.lua b/source/dangerSprite.lua new file mode 100644 index 0000000..b245e34 --- /dev/null +++ b/source/dangerSprite.lua @@ -0,0 +1,27 @@ +local pd = playdate +local gfx = playdate.graphics + +class("Danger").extends(AnimatedSprite) + +function Danger:init(x, y, baleba) + local dangerImageTable = gfx.imagetable.new("sprites/danger") + Danger.super.init(self, dangerImageTable) + + self:moveTo(x, y) + + self:addState("run", 1,5, {tickStep = 2}) + + self:setDefaultState("run") + + self:playAnimation() + + self:setZIndex(11) + + self:setTag(7) + + self.baleba = baleba +end + +function Danger:update() + self:updateAnimation() +end \ No newline at end of file diff --git a/source/level.lua b/source/level.lua index aa1a428..dde277f 100644 --- a/source/level.lua +++ b/source/level.lua @@ -4,6 +4,8 @@ local gfx = playdate.graphics import "player" import "ground" import "backgroundSprite" +import "balebaSprite" +import "dangerSprite" class("Level").extends() @@ -49,6 +51,26 @@ function Level:init() Level.telemLostSound = playdate.sound.fileplayer.new( "audio/telemko" ) Level.telemLostSoundPlayed = false + + Level.balebas = {} + + for i=1, 3 do + Level.balebas[i] = Baleba(math.random(410, 900), math.random(10, 210), player) + Level.balebas[i]:add() + end + + t = playdate.timer.new(10000) + t.repeats = true + t.timerEndedCallback = function() + if #Level.balebas >= 6 then + return + end + + local k = #Level.balebas+1 + Level.balebas[k] = Baleba(math.random(410, 900), math.random(10, 210), player) + Level.balebas[k]:add() + print("Baleba added") + end end function Level:update() diff --git a/source/lib/pdParticles.lua b/source/lib/pdParticles.lua new file mode 100644 index 0000000..ae7a7c3 --- /dev/null +++ b/source/lib/pdParticles.lua @@ -0,0 +1,626 @@ +local particles = {} +local precision = 1 + +-- base particle class +class("Particle").extends() +function Particle:init(x, y) + self.x = x or 0 + self.y = y or 0 + self.size = {1,1} + self.spread = {0,359} + self.speed = {1,1} + self.acceleration = {0, 0} + self.thickness = {0,0} + self.lifespan = {1,1} + self.decay = 1 + self.particles = {} + self.colour = playdate.graphics.kColorBlack + self.bounds = {0,0,0,0} + self.mode = 0 + if self.type == 2 then -- polys + self.points={3,3} + self.angular={0,0} + self.rotation={0,359} + elseif self.type == 3 then -- images + self.angular={0,0} + self.image = playdate.graphics.image.new(1,1) + self.table = nil + self.rotation = {0,359} + end + + particles[#particles+1] = self + +end + +function Particle:remove() + for i = 1, #particles, 1 do + if particles[i] == self then + table.remove(particles, i) + break + end + end +end + +-- [[ SETTERS AND GETTERS ]] -- + +-- movement +function Particle:moveTo(x,y) + self.x = x + self.y = y +end + +function Particle:moveBy(x,y) + self.x += x + self.y += y +end + +function Particle:getPos() + return self.x, self.y +end + +-- size +function Particle:setSize(min, max) + self.size = {min, max or min} +end + +function Particle:getSize() + return self.size[1], self.size[2] +end + +-- mode +function Particle:setMode(mode) + self.mode = mode +end + +function Particle:getMode() + return self.mode +end + +-- spread +function Particle:setSpread(min,max) + self.spread = {min, max or min} +end + +function Particle:getSpread() + return self.spread[1], self.spread[2] +end + +-- speed +function Particle:setSpeed(min,max) + self.speed = {min, max or min} +end + +function Particle:getSpeed() + return self.speed[1], self.speed[2] +end + +-- acceleration +function Particle:setAcceleration(min,max) + self.acceleration = {min, max or min} +end + +function Particle:getAcceleration() + return self.acceleration[1], self.acceleration[2] +end + +-- thickness +function Particle:setThickness(min, max) + self.thickness = {min, max or min} +end + +function Particle:getThickness() + return self.thickness[1], self.thickness[2] +end + +-- lifespan +function Particle:setLifespan(min,max) + self.lifespan = {min, max or min} +end + +function Particle:getLifespan() + return self.lifespan[1], self.lifespan[2] +end + +-- colour +function Particle:setColor(colour) + self.colour = colour +end + +function Particle:getColor() + return self.colour +end + +function Particle:setColour(colour) + self.colour = colour +end + +function Particle:getColour() + return self.colour +end + +-- bounds +function Particle:setBounds(x1, y1, x2, y2) + self.bounds = {x1,y1,x2,y2} +end + +function Particle:getBounds() + return self.bounds[1],self.bounds[2],self.bounds[3],self.bounds[4] +end + +-- decay +function Particle:setDecay(decay) + self.decay = decay +end + +function Particle:getDecay() + return self.decay +end + +-- particles +function Particle:getParticles() + return self.particles +end + +function Particle:clearParticles() + self.particles = {} +end + +function Particle:update() +end + +-- [[ PARTICLE MODES ]] -- + +local function decay(partlist, decay) + for part = 1, #partlist, 1 do + local particle = partlist[part] + particle.size -= decay + + if particle.size <= 0 then + particle.size = 0 + end + + partlist[part] = particle + end + + for part = #partlist, 1, -1 do + local particle = partlist[part] + if particle.size <= 0 then + table.remove(partlist,part) + end + end + + return partlist +end + +local function disappear(partlist) + for part = 1, #partlist, 1 do + local particle = partlist[part] + particle.lifespan -= .1 + end + for part = #partlist, 1, -1 do + local particle = partlist[part] + if particle.lifespan <= 0 then + table.remove(partlist,part) + end + end + + return partlist +end + +local function loop(partlist, bounds) + if bounds[3] > bounds[1] and bounds[4] > bounds[2] then + local xDif , yDif = bounds[3] - bounds[1], bounds[4] - bounds[2] + for part = 1, #partlist, 1 do + local particle = partlist[part] + if particle.x > bounds[3] then particle.x -= xDif + elseif particle.x < bounds[1] then particle.x += xDif end + if particle.y > bounds[4] then particle.y -= yDif + elseif particle.y < bounds[2] then particle.y += yDif end + end + end + + return partlist +end + +local function stay(partlist, bounds) + if bounds[3] > bounds[1] and bounds[4] > bounds[2] then + local xDif , yDif = bounds[3] - bounds[1], bounds[4] - bounds[2] + for part = #partlist, 1, -1 do + local particle = partlist[part] + if particle.x > bounds[3] then table.remove(partlist,part) + elseif particle.x < bounds[1] then table.remove(partlist,part) + elseif particle.y > bounds[4] then table.remove(partlist,part) + elseif particle.y < bounds[2] then table.remove(partlist,part) end + end + end + + return partlist +end + +class("ParticleCircle", {type = 1}).extends(Particle) + +function ParticleCircle:create(amount) + for i = 1, amount, 1 do + local part = { + x = self.x, + y = self.y, + dir = math.random(self.spread[1],self.spread[2]), + size = math.random(self.size[1],self.size[2]) * precision, + speed = math.random(self.speed[1],self.speed[2]) * precision, + acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision, + lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision, + thickness = math.random(self.thickness[1],self.thickness[2]), + decay = self.decay + } + + self.particles[#self.particles+1] = part + end +end + +function ParticleCircle:add(amount) + self:create(amount) +end + +function ParticleCircle:update() + local w = playdate.graphics.getLineWidth() + local c = playdate.graphics.getColor() + playdate.graphics.setColor(self.colour) + for part = 1, #self.particles, 1 do + local circ = self.particles[part] + if circ.thickness < 1 then + playdate.graphics.fillCircleAtPoint(circ.x,circ.y,circ.size) + else + playdate.graphics.setLineWidth(circ.thickness) + playdate.graphics.drawCircleAtPoint(circ.x, circ.y, circ.size) + end + + circ.x += math.sin(math.rad(circ.dir)) * circ.speed + circ.y -= math.cos(math.rad(circ.dir)) * circ.speed + + circ.speed += circ.acceleration / 100 + + self.particles[part] = circ + end + playdate.graphics.setLineWidth(w) + playdate.graphics.setColor(c) + if self.mode == 1 then + decay(self.particles, self.decay) + + elseif self.mode == 0 then + disappear(self.particles) + + elseif self.mode == 2 then + loop(self.particles, self.bounds) + + else + stay(self.particles, self.bounds) + + end +end + +class("ParticlePoly", {type = 2}).extends(Particle) + +function ParticlePoly:getPoints() + return self.points[1], self.points[2] +end + +function ParticlePoly:setPoints(min,max) + self.points = {min, max or min} +end + +-- angular +function ParticlePoly:getAngular() + return self.angular[1], self.angular[2] +end + +function ParticlePoly:setAngular(min,max) + self.angular = {min, max or min} +end + +function ParticlePoly:getRotation() + return self.rotation[1], self.rotation[2] +end + +function ParticlePoly:setRotation(min,max) + self.rotation = {min, max or min} +end + +function ParticlePoly:create(amount) + for i = 1, amount, 1 do + local part = { + x = self.x, + y = self.y, + dir = math.random(self.spread[1],self.spread[2]), + size = math.random(self.size[1],self.size[2]) * precision, + speed = math.random(self.speed[1],self.speed[2]) * precision, + acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision, + lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision, + thickness = math.random(self.thickness[1],self.thickness[2]) * precision, + angular = math.random(self.angular[1],self.angular[2]) * precision, + points = math.random(self.points[1], self.points[2]), + decay = self.decay, + rotation = math.random(self.rotation[1],self.rotation[2]) + } + + self.particles[#self.particles+1] = part + end +end + +function ParticlePoly:add(amount) + self:create(amount) +end + +function ParticlePoly:update() + local w = playdate.graphics.getLineWidth() + local c = playdate.graphics.getColor() + playdate.graphics.setColor(self.colour) + for part = 1, #self.particles, 1 do + local poly = self.particles[part] + local polygon = {} + local degrees = 360 / poly.points + for point = 1, poly.points, 1 do + polygon[#polygon+1] = poly.x + math.sin(math.rad(degrees * point + poly.rotation)) * poly.size + polygon[#polygon+1] = poly.y - math.cos(math.rad(degrees * point + poly.rotation)) * poly.size + end + if poly.thickness < 1 then + playdate.graphics.fillPolygon(table.unpack(polygon)) + else + playdate.graphics.setLineWidth(poly.thickness) + playdate.graphics.drawPolygon(table.unpack(polygon)) + end + + poly.x += math.sin(math.rad(poly.dir)) * poly.speed + poly.y = poly.y - math.cos(math.rad(poly.dir)) * poly.speed + + poly.rotation += poly.angular + + poly.speed += poly.acceleration / 100 + + self.particles[part] = poly + end + playdate.graphics.setLineWidth(w) + playdate.graphics.setColor(c) + + if self.mode == 1 then + decay(self.particles, self.decay) + + elseif self.mode == 0 then + disappear(self.particles) + + elseif self.mode == 2 then + loop(self.particles, self.bounds) + + else + stay(self.particles, self.bounds) + + end +end + +class("ParticleImage", {type = 3}).extends(Particle) + +function ParticleImage:getAngular() + return self.angular[1], self.angular[2] +end + +function ParticleImage:setAngular(min,max) + self.angular = {min, max or min} +end + +function ParticleImage:getRotation() + return self.rotation[1], self.rotation[2] +end + +function ParticleImage:setRotation(min,max) + self.rotation = {min, max or min} +end + +function ParticleImage:setImage(image) + self.image = image + self.table = nil +end + +function ParticleImage:setImageTable(image) + self.image = nil + self.table = image +end + +function ParticleImage:getImage() + return self.image +end + +function ParticleImage:getImageTable() + return self.table +end + +function ParticleImage:create(amount) + if self.image ~= nil then + for i = 1, amount, 1 do + local part = { + x = self.x, + y = self.y, + dir = math.random(self.spread[1],self.spread[2]), + size = math.random(self.size[1],self.size[2]) * precision, + speed = math.random(self.speed[1],self.speed[2]) * precision, + acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision, + lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision, + thickness = math.random(self.thickness[1],self.thickness[2]), + angular = math.random(self.angular[1],self.angular[2]) * precision, + decay = self.decay, + image = self.image, + rotation = math.random(self.rotation[1],self.rotation[2]) + } + + self.particles[#self.particles+1] = part + end + else + for i = 1, amount, 1 do + local part = { + x = self.x, + y = self.y, + dir = math.random(self.spread[1],self.spread[2]), + size = math.random(self.size[1],self.size[2]) * precision, + speed = math.random(self.speed[1],self.speed[2]) * precision, + acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision, + lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision, + thickness = math.random(self.thickness[1],self.thickness[2]), + angular = math.random(self.angular[1],self.angular[2]) * precision, + decay = self.decay, + image = self.table[math.random(#self.table)], + rotation = math.random(self.rotation[1],self.rotation[2]) + } + + self.particles[#self.particles+1] = part + end + end +end + +function ParticleImage:add(amount) + self:create(amount) +end + +function ParticleImage:update() + for part = 1, #self.particles, 1 do + local img = self.particles[part] + + img.image:drawRotated(img.x,img.y,img.rotation,img.size) + + img.rotation += img.angular + + img.x += math.sin(math.rad(img.dir)) * img.speed + img.y = img.y - math.cos(math.rad(img.dir)) * img.speed + + img.speed += img.acceleration / 100 + + self.particles[part] = img + end + + if self.mode == 1 then + decay(self.particles, self.decay) + + elseif self.mode == 0 then + disappear(self.particles) + + elseif self.mode == 2 then + loop(self.particles, self.bounds) + + else + stay(self.particles, self.bounds) + + end +end + +class("ParticleImageBasic", {type = 3}).extends(ParticleImage) + +function ParticleImageBasic:update() + for part = 1, #self.particles, 1 do + local img = self.particles[part] + + img.image:drawScaled(img.x,img.y,img.size) + + img.rotation += img.angular + + img.x += math.sin(math.rad(img.dir)) * img.speed + img.y = img.y - math.cos(math.rad(img.dir)) * img.speed + + img.speed += img.acceleration / 100 + + self.particles[part] = img + end + + if self.mode == 1 then + decay(self.particles, self.decay) + + elseif self.mode == 0 then + disappear(self.particles) + + elseif self.mode == 2 then + loop(self.particles, self.bounds) + + else + stay(self.particles, self.bounds) + + end +end + +class("ParticlePixel").extends(Particle) + +function ParticlePixel:create(amount) + for i = 1, amount, 1 do + local part = { + x = self.x, + y = self.y, + dir = math.random(self.spread[1],self.spread[2]), + speed = math.random(self.speed[1],self.speed[2]) * precision, + acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision, + lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision, + } + + self.particles[#self.particles+1] = part + end +end + +function ParticlePixel:add(amount) + self:create(amount) +end + +function ParticlePixel:update() + local c = playdate.graphics.getColor() + playdate.graphics.setColor(self.colour) + for part = 1, #self.particles, 1 do + local pix = self.particles[part] + + playdate.graphics.drawPixel(pix.x,pix.y,pix.size) + + pix.x += math.sin(math.rad(pix.dir)) * pix.speed + pix.y -= math.cos(math.rad(pix.dir)) * pix.speed + + pix.speed += pix.acceleration / 100 + + self.particles[part] = pix + end + playdate.graphics.setColor(c) + + if self.mode == 0 then + disappear(self.particles) + + elseif self.mode == 2 then + loop(self.particles, self.bounds) + + else + stay(self.particles, self.bounds) + + end +end + +-- [[ GLOBAL PARTICLE STUFF ]] -- + +class("Particles").extends() + +Particles.modes = {DISAPPEAR = 0, DECAY = 1, LOOP = 2, STAY = 3} + +function Particles:update() + for particle = 1, #particles, 1 do + particles[particle]:update() + end +end + +function Particles:clearAll() + for part = 1, #particles, 1 do + particles[part]:clearParticles() + end +end + +function Particles:removeAll() + local lb = #particles + for part = 1, #particles, 1 do + particles[part] = nil + end +end + +function Particles:setPrecision(prec) + precision = prec +end + +function Particles:getPrecision() + return precision +end \ No newline at end of file diff --git a/source/main.lua b/source/main.lua index 5b59c7b..25b811e 100644 --- a/source/main.lua +++ b/source/main.lua @@ -26,6 +26,8 @@ gfx.setFont(font) -- Libraries import "lib/AnimatedSprite" +import "lib/pdParticles" + playdate.display.setRefreshRate(50) -- Game @@ -40,6 +42,7 @@ local function initialize() -- Init all the things! level = Level() playdate.resetElapsedTime() + end initialize() @@ -48,6 +51,7 @@ function pd.update() gfx.sprite.update() pd.timer.updateTimers() pd.drawFPS(10,0) + Particles:update() if level then level.update() diff --git a/source/player.lua b/source/player.lua index 889204a..8100629 100644 --- a/source/player.lua +++ b/source/player.lua @@ -34,7 +34,8 @@ function Player:init(x, y, gameManager) self.maxXSpeed = 2 self.maxYSpeed = 5 - self.fallSpeed = 0.4 + self.fallSpeed = 0.05 + self.maxFallSpeed = 0.4 -- Player State self.touchingGround = false @@ -116,14 +117,19 @@ function Player:handleMovementAndCollisions() local collisions local length = 0 - if self.x >= 20 and self.x <= 380 then - _, _, collisions, length = self:moveWithCollisions(self.x + self.xVelocity, self.y + self.yVelocity) - elseif self.x == 380 then - self.x = 380 - else - self.x = 20 + if Player.dead then + return end + local xVel = self.xVelocity + + if (self.x < 20 and xVel < 0) or (self.x > 380 and xVel > 0) then + xVel = 0 + end + + _, _, collisions, length = self:checkCollisions(self.x + xVel, self.y + self.yVelocity) + + self.touchingGround = false self.touchingCeiling = false self.touchingWall = false @@ -145,14 +151,29 @@ function Player:handleMovementAndCollisions() end end + if collisionTag == 3 then Player.dead = true self:changeState("boom") - -- elseif collisionTag == TAGS.Pickup then - -- collisionObject:pickUp(self) + return + elseif collisionTag == 154 then + Player.dead = true + self:changeState("boom") + + local particleB = ParticlePoly(self.x, self.y) + particleB:setThickness(2) + particleB:setAngular(-15,15) + particleB:setSize(1, 2) + particleB:setSpeed(1, 3) + particleB:setMode(Particles.modes.STAY) + particleB:setBounds(0,0,400,240) + particleB:setColour(gfx.kColorXOR) + particleB:add(20) + collisionObject:remove() -- TODO: add animation + return end end - + self:moveTo(self.x + xVel, self.y + self.yVelocity) end function Player:isMovingRight() @@ -175,6 +196,11 @@ function Player:handleDischarge(state) Player.bat = 0 self.fallSpeed = 2 self:changeToDownState() + return + end + + if Player.bat < 5000 then + self.fallSpeed = self.maxFallSpeed end if state == "run" then @@ -185,11 +211,17 @@ function Player:handleDischarge(state) Player.dischargeRate = 1 end + if self.y < 5 then + Player.dischargeRate = 20 + end + Player.bat = Player.bat - Player.dischargeRate end function Player:update() self:updateAnimation() + self:handleMovementAndCollisions() + if Player.dead then return end @@ -198,6 +230,4 @@ function Player:update() self:handleDischarge(state) self:handleGroundInput(state) - - self:handleMovementAndCollisions() end \ No newline at end of file diff --git a/source/sprites/baleba-table-64-64.png b/source/sprites/baleba-table-64-64.png new file mode 100644 index 0000000000000000000000000000000000000000..4a86467a14ba0d2f09ffe12c354844533e35c3ce GIT binary patch literal 1510 zcmcgs`#aMM9RAMbkb@de4-?X(n^ZdvQzPjzA<>OtE2M{Il8#x~h0Qs#93tv0EQZQS zZZVfrE|r|)y3H}h(P1iVEK?iC>~iKjPya$cyr1{|yr1{;>zi>Vz}v*g$_M}e6JH<1 zIRF4@C2+*>JMCP$SkbN}5bm7!F`$NHJ*!<1Q6aw3{{Fxotu_Qev=~5dJ)(t`7671I z0s?fk3|jX}biQ6eK#A^G4IEhyE_&rY0Ra6ozKElNNuc@s)P#?A#)x-}HBGqWsMSwK z;2*muM|;Glx-19k*8uKPd;3Z%pl1hw437Xho2cJLMr>MfQiy&A^lq4~uvqHb3CXKC zw8pkh4dD+Mb0!TocA14VX(%~tr&g4Wk`(e@p%44zVfP#f;|gDJ7s>)E8b^@fx48wL z0McjUkb8EcWt!_+85L!mU>*CZXf$LsK@tPuu7IxG;=J*kH85S)AHhV7c@329irHGP z!K?vUX?&bu)=qR`7d#VM_oc$xlszs39UK~XefFLoQ#Bnd`{V#XQ>UlMx4G(iA?~jR~^_7u6D}h>^Zt1EZ(f#`}E8*wzlxVham2fQcS2M z=ZGXrs1a^=aBTKe0laxvX3{Y330Ep5RaWH=J&-8lCcIN>=p^dk=%=}r`#a}i~AAc zh7;q(WH6SX3bV|b`eV(}t?=@!v8!qSe$&2f`31*2y^AyJg=EIf$s)Nx;YqEZwcirR zwMpICDEFt2&@gzQ?GEYb_wD!lJW;jDM8XNYOj%Xr5{$8kz4gW?n^-fmts_@h_KER; zGE~Vyvn+?732P%Fl7TrqO@O;@QWk7??p}j;*6JNl0kVSOJRSPURg(s z(d5j!LAjb=)kuCoVINEGj^(!Ys?l4iL8( z_<{7W;Ke0DWW*AzcjSgQAN|~=(NF9EOuo?x&%@0c-cVSi?#Qm+^ zV4ORY(sQOmi!E#=6KJD;sOp|#B z!+3!T3 zJGRie@ZZ2XO>{OET`C(RA*zmzVxEi6Et@7Mw6@~U9O#8y@FA)<`L7Z+Ww-q-lZ zHX55@ActcI%y}~y5w@*WV(ib=6xWT3ZC}y(wrw6Sh*)%reK7{Rl*EK}No_o8>BzLG z;HIZN`@d|s=aO|=_KXE}2h5ju`wfN3R~j3ZMbl&UDGdt{OprKAH53HS+pEM;pzRHT9KV6wpDKY-YY}H^Ub7&Uk lqP8h$Q;+l;^;No|QrD7oVz=zQ#fSBd_4Nus)EtZW`9J&>q5uE@ literal 0 HcmV?d00001 diff --git a/source/sprites/danger-table-64-64.png b/source/sprites/danger-table-64-64.png new file mode 100644 index 0000000000000000000000000000000000000000..5894e853879fd7be93a1c297f9892cae9f0e3833 GIT binary patch literal 1039 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Yo9XQy4WcKrQazKi)ILO_JVcj{Imq3n7YJ_K+ zuP=iZkj=rs*q+J20#X9RQb5eWz`THwff-0Mg2Wau!DJa77BIuu3=SZLE1ACp0+m;K zx;TbZFuuLrT{PQ3fc3(T%m4qUZr_orAU(N3b;1+dd6~^!40ktYKQ;aP*JT-yL+~uD<kty5;XKxn_2tdv(o+Lf=`B z9!O>Tou5{oE57;o$EtOYy_ojqHgv_U+>qz|;4F*ZP3fxJ49RExIA!INd>S158t<7O zm=k)_Z|+}Vr)Pg=ZqDDY%yqtJ8)H_}p{Ct)7#dv@w(u52?oiYwd<^waz?e@{CH=Ed0+XRN4d*V zPTT!GR9tm-=i?vuZywt3ew!h^=g^JAF>;^hU1E{VKC-9q&c{pL)2HqIcC?{7@4|1k z8EZK=J-vH*apd!qTW^+29l!TK^Ut(eL(y8jKO!HNe~fGSYWqFza#GydJ%w)ni+;8>U<};?{{41S&djGW(FVdQ&MBb@0NLTKi2wiq literal 0 HcmV?d00001 diff --git a/source/sprites/tank.png b/source/sprites/tank.png new file mode 100644 index 0000000000000000000000000000000000000000..04eff9119b057f4a2cce24b702c54b6c0fdb35ab GIT binary patch literal 5522 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 toghATl&(#G=PCZ3H}-&eyZpza^1oIf{))EU+