205 lines
5.2 KiB
Lua
205 lines
5.2 KiB
Lua
-- 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 <const> = playdate.graphics
|
|
local geom <const> = playdate.geometry
|
|
|
|
local BASE_CHARGE_FACTOR <const> = 15
|
|
local BASE_WEAPON_CHARGE_SPEED <const> = 1000
|
|
|
|
-- the amount of charge needed to increase the shot power by 1 at each power level
|
|
local WEAPON_CHARGE_LOOKUP <const> = {
|
|
[0]=5,
|
|
[1]=15,
|
|
[2]=30,
|
|
[3]=50
|
|
}
|
|
|
|
-- size of bullet at each power level
|
|
local POWER_SIZE_LOOKUP <const> = {
|
|
[1] = 2,
|
|
[2] = 4,
|
|
[3] = 6,
|
|
[4] = 10,
|
|
}
|
|
|
|
-- damage of bullet at each power level
|
|
local POWER_DAMAGE_LOOKUP <const> = {
|
|
[1] = 1,
|
|
[2] = 3,
|
|
[3] = 5,
|
|
[4] = 10,
|
|
}
|
|
|
|
local SPEED_SIZE_LOOKUP <const> = {
|
|
[1] = 10,
|
|
[2] = 5,
|
|
[3] = 3,
|
|
[4] = 1,
|
|
}
|
|
|
|
-- animations table
|
|
local animations <const> = {
|
|
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
|