-- Code for the player's ship. import "CoreLibs/object" import "CoreLibs/graphics" import "CoreLibs/sprites" import "CoreLibs/timer" import "entity" import "bullet" import "ui" local gfx = playdate.graphics local geom = playdate.geometry local BASE_CHARGE_FACTOR = 15 local BASE_WEAPON_CHARGE_SPEED = 1000 -- the amount of charge needed to increase the shot power by 1 at each power level local WEAPON_CHARGE_LOOKUP = { [0]=5, [1]=15, [2]=30, [3]=50 } -- size of bullet at each power level local POWER_SIZE_LOOKUP = { [1] = 2, [2] = 4, [3] = 6, [4] = 10, } -- damage of bullet at each power level local POWER_DAMAGE_LOOKUP = { [1] = 1, [2] = 3, [3] = 5, [4] = 10, } local SPEED_SIZE_LOOKUP = { [1] = 10, [2] = 5, [3] = 3, [4] = 1, } -- animations table local animations = { defend=gfx.imagetable.new("images/kani/defend"), defend_reverse=gfx.imagetable.new("images/kani/defend_reverse"), } class("Kani").extends(Entity) function Kani:init(ui) Kani.super.init(self, gfx.image.new("images/kani/static.png"), 100) self.type = 'kani' self:setGroupMask(0x2) self:setCollidesWithGroupsMask(0xd) self:setCollideRect(1,12,25,24) self.reserveCharge = 100 -- controls the speed the crank recharges the player's reserves. A lower number allows faster charging. -- should never be set to 0 self.chargeFactor = BASE_CHARGE_FACTOR -- weapon variables self.weaponPower = 0 self.firingMode = false self.weaponChargeSpeed = BASE_WEAPON_CHARGE_SPEED -- speed in ms between weapon charge levels. Level 2 always takes half this time. -- the UI gets passed in to Kani so that we can update pieces of it easily. self.ui = ui self.fsm:addState("DEFEND", self.runDefend, self.onDefend, self.onDefendExit) end function Kani:chargeReserve(change) if change <= 0 then return end self:setReserveCharge(math.min(self.reserveCharge + change / self.chargeFactor, 100)) end function Kani:chargeShot() if self.weaponPower >= 4 then return -- weapon is fully charged end local requiredPower = WEAPON_CHARGE_LOOKUP[self.weaponPower] -- We use math.ceil here so that any fractional charge rounds up. -- This ensures we can always use our last bit of juice for a level 1 shot, -- and smooths out play experience around fully charged values. local effectiveCharge = math.ceil(self.reserveCharge) if effectiveCharge >= requiredPower then self:setReserveCharge(effectiveCharge - requiredPower) self:setWeaponPower(self.weaponPower+1) end end function Kani:setReserveCharge(newCharge) if newCharge < 0 then newcharge = 0 end self.reserveCharge = newCharge self.ui.chargeMeter:setValue(self.reserveCharge) end function Kani:setWeaponPower(newPwr) self.weaponPower = newPwr self.ui.weaponPowerMeter:setValue(math.max(self.weaponPower-1, 0)) end function Kani:fire() self.firingTimer:remove() if self.weaponPower <= 0 then return end local bullet = Bullet( POWER_SIZE_LOOKUP[self.weaponPower], POWER_DAMAGE_LOOKUP[self.weaponPower], geom.vector2D.new(SPEED_SIZE_LOOKUP[self.weaponPower],0), 0x4 ) bullet:moveTo(self.x+16, self.y) bullet:add() self:setWeaponPower(0) end function Kani:damage(amount) Kani.super.damage(self, amount) self.ui.healthMeter:setValue(self.health) print("Kani health now at " .. self.health) -- debug if self.health == 0 then -- TODO: end game here end end -- move that crab! function Kani:runReady() -- input handling if playdate.buttonIsPressed(playdate.kButtonDown) then self.vector.dy = 3 elseif playdate.buttonIsPressed(playdate.kButtonUp) then self.vector.dy = -3 else self.vector.dy = 0 end if playdate.buttonIsPressed(playdate.kButtonLeft) then self.vector.dx = -3 elseif playdate.buttonIsPressed(playdate.kButtonRight) then self.vector.dx = 3 else self.vector.dx = 0 end local change = select(1, playdate.getCrankChange()) if not playdate.buttonIsPressed(playdate.kButtonA) then self:chargeReserve(change) end if playdate.buttonJustPressed(playdate.kButtonA) then self.firingTimer = playdate.timer.keyRepeatTimerWithDelay(self.weaponChargeSpeed / 2, self.weaponChargeSpeed, self.chargeShot, self) end if playdate.buttonJustReleased(playdate.kButtonA) then self:fire() end if playdate.buttonIsPressed(playdate.kButtonB) and self.reserveCharge > 0 then self.fsm:changeState("DEFEND") end -- collision handling local collisions = Kani.super.runReady(self) for i=0, #collisions, 1 do -- TODO: handle player-triggered collisions end end function Kani:onDefend() self.armor = 5 self:animate(animations.defend, 50) end function Kani:onDefendExit() self.armor = 0 self:animate(animations.defend_reverse, 50, false, true) end function Kani:runDefend() if self.reserveCharge == 0 or not playdate.buttonIsPressed(playdate.kButtonB) then self.fsm:changeState("READY") return end self:setReserveCharge(self.reserveCharge - 1) end