Skip to content

Instantly share code, notes, and snippets.

@AntonioCiolino
Created September 6, 2013 20:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AntonioCiolino/a9f5189a0fd8a999d924 to your computer and use it in GitHub Desktop.
Save AntonioCiolino/a9f5189a0fd8a999d924 to your computer and use it in GitHub Desktop.
Cofender Release v1.0.2 -Cofender - a defender clone written with Codea
Cofender Tab Order Version: 1.0.2
------------------------------
This file should not be included in the Codea project.
#AnimatedDraw
#Attacker
#Draw
#Explode
#Bullet
#Firework
#Particle
#Saucer
#FlashScore
#Guy
#Invader
#MineLayer
#ScreenEnd
#ScreenGame
#ScreenLevelComplete
#ScreenOptions
#ScreenPaused
#ScreenShipExplode
#ScreenSplash
#ScreenTitle
#Shields
#Ship
#Helpers
#Main
#Spawner
#Star
----
AnimatedDraw = class()
--animated draw class.
--a simple animated loop class
--Currently only allows for a horizontal version of a sprite.
-- In this version, we do not allow Y-axis alteration.
--we are doing simple timer tests for animations, no fancy Timer class.
function AnimatedDraw:init(params)
--base error checking.
assert(params.img, "AnimatedDraw items must have an image to cycle.")
self.img = params.img
self.fps = params.fps or 5 -- fps
self.maxframe = params.frames or 5 --number of frames in the strip
self.maxrows = params.rows or 1 --number of rows in this sheet
self.size = params.size or vec2(16,16) -- size of animation image
self.direction = params.direction or 1 --1, 0, -1...determines move direction
self.mesh = mesh()
self.mesh.texture=self.img --once set, this isn't let go...we'd have to reset it
self.pos = params.pos or vec2(0,0) -- position of the item
self.move = params.move or vec2(0,0) --when flipping frames what is the offset
--optimization variables.
self.curframe = 0 --Current draw frame
self.row = params.row or 1 --which row of the altas?
self.lastDrawTime = ElapsedTime --last time a NEW frame was drawn.
self.diffTime = 1/ self.fps --the difference in time to wait for new frames
end
-- This assumes that all frames in an image passed are horizontal and are all supposed to
--be drawn. if we want to jump around or index frames, we don't so that in this version.
function AnimatedDraw:draw()
--generate current frame
if ElapsedTime > self.lastDrawTime+self.diffTime then
self.curframe = self.curframe + 1
if self.curframe >= self.maxframe then self.curframe=0 end --restart
self.lastDrawTime=ElapsedTime
end
-- Codea does not automatically call this method
self.mesh:clear()
--if we want to move the position of the item, do it here.
self.pos.x = self.pos.x + (self.move.x * self.direction)
self.pos.y = self.pos.y + (self.move.y * self.direction)
--create the mesh and texture position
local obj=self.mesh:addRect(self.pos.x,self.pos.y,self.size.x,self.size.y)
local obj2=self.mesh:addRect(self.pos.x+10000,self.pos.y,self.size.x,self.size.y)
local cols= self.curframe
self.mesh:setRectTex(obj,
self.curframe / self.maxframe, --current col position
self.row/self.maxrows,
1/self.maxframe, --ratio of cols
1/self.maxrows) --ratio of rows
self.mesh:setRectTex(obj2,
self.curframe / self.maxframe, --current col position
self.row/self.maxrows,
1/self.maxframe, --ratio of cols
1/self.maxrows) --ratio of rows
--draw the mesh!
self.mesh:draw()
end
function AnimatedDraw:touched(touch)
-- Codea does not automatically call this method
end
function AnimatedDraw:ResetTexture(img)
self.mesh = mesh()
self.mesh.texture=img --once set, this isn't let go...we'd have to reset it
end
----
Attacker = class()
--class used to attack the player. Invader and others should subclass/extend this.
function Attacker:init(params)
assert (params.name, "Enemies must have a name for indexing")
self.name = params.name
self.pos = params.pos or vec2(0,0)
self.speed = params.speed or 1 --movement speed
self.direction = 1 --or -1
self.looking= params.looking or true --set to false when we acquired
self.isMutant = false --set to true if we are able to mutate
self.FireSpeed = params.FireSpeed or 7 --how fast is our bullet?
self.myHuman = nil --only if we are holding someone...
self.maxCycles= params.cycles or 120 --frames before enemy can shoot again.
self.cycles = params.cycles or 120
self.img=params.img
self.width = self.img.width
self.height = self.img.height
self.points = params.points or 150
self.impactDamage = params.impactDamage or 10 --imapct damage, not bullet damage
self.damage = params.damage or 1 --bullet damage
self.speed=self.speed+wave/10 --wave delta increase on movement speed.
self.health = 1 --all things have simple health. Spawner is an exception
self.hudColor = params.hudColor or color(213, 193, 12, 255)
self.oldDelta=vec2(0,0)
--pass drawparams for animation
self.drawparams = params.drawparams or
{img = self.img, fps= 1, size = vec2(self.img.width,self.img.height), pos = self.pos,
move=vec2(0, 0), self.direction , frames = 1, rows = 1, row=0}
self.d = AnimatedDraw(self.drawparams)
end
function Attacker:Heartbeat()
self:Move()
self:Shoot()
self:CheckForImpact()
self:draw()
end
function Attacker:Move()
local delta = (worldpos + player.screenpos) - self.pos
if math.abs(delta.x)>WORLDSIZE/2 then delta=delta *-1 end -- faster the other way...
delta = delta + self.oldDelta*self.speed --
delta = delta:normalize()
self.pos.x = self.pos.x + (delta.x) * self.speed
self.pos.y = self.pos.y + (delta.y) * self.speed
self.pos = CorrectPosition(self.pos)
self.oldDelta=delta
end
function Attacker:draw()
--sprite(self.img, self.pos.x-worldpos.x, self.pos.y)
--sprite(self.img, 10000+self.pos.x-worldpos.x, self.pos.y)
if (debug==true) then
--text(tostring(self.pos), self.pos.x-worldpos.x, self.pos.y)
end
self.d.pos = vec2(self.pos.x-worldpos.x, self.pos.y)
self.d:draw()
--hud
pushMatrix()
stroke(self.hudColor)
ellipse((self.pos.x)/ratio+HUD_LEFT, self.pos.y/ratioy+(TOP), 2,2)
popMatrix()
end
function Attacker:touched(touch)
-- Codea does not automatically call this method
end
function Attacker:Shoot()
local target=nil
self.cycles = self.cycles - 1
if self.cycles < 1 then
--call only one or else you get homing bullets!
target = worldpos + player.screenpos
if self.predictive == true then
target = worldpos + player.screenpos
target.x =target.x +steerpos.x*10
target=CorrectPosition(target)
-- 18 is half ahip max speed. hack!! 10 is 1/6 sec
end
local vector = target - self.pos
vector = vector:normalize()
local dist = self.pos:dist(player.screenpos + worldpos)
if (dist< 1200 and dist > 20) then --don't fire if TOO close! Stray bullets!
self.cycles = self.maxCycles --again. should make a CONST
sound(DATA, "ZgNAJwBDE1xPITlPiEBdPW8lCD8AAAAARQBifzRAOFQiGloU")
b = Bullet({
pos = vec2(self.pos.x, self.pos.y),
name = "Bullet"..tostring(math.random(1000)),
vector = vector , damage = self.damage,
impactDamage = self.impactDamage,
speed = self.FireSpeed
})
table.insert(bullets, b)
end
end
end
function Attacker:CheckForImpact()
local rbx = self.pos.x - player.width
local rax = self.pos.x + player.width
local rby = self.pos.y - player.height
local ray = self.pos.y + player.height
if (rbx < player.screenpos.x+worldpos.x and rax > player.screenpos.x+worldpos.x and
rby < player.screenpos.y and ray > player.screenpos.y) then
player:IsHit(self.impactDamage)
self:IsHit(player.shields) --player damages item too!
self.points=math.ceil(math.ceil(self.points/100)*25) -- if player bumps, reduce points!
RemoveEnemy(self.name)
end
end
function Attacker:IsHit(damage)
--shot
self.health = self.health - damage
sound(SOUND_HIT, 8780)
sound(DATA, "ZgNADgJBQkFHQ0BAAAAAAMK++z4tmeg9NABAf0JAQEAVO0BB", .2)
end
----
-- anything fired is a bullet...anything shot
Bullet = class()
function Bullet:init(params)
assert(params.name, "All bullets must have a name")
self.name = params.name
self.pos = params.pos
self.speed = params.speed or 10
self.damage = params.damage or 1 --
-- self.owner = params.owner --who fired? not neeeded
self.cycles = params.cycles or 60 --num frames to keep bullet
self.vector = params.vector
self.img = params.img or readImage("Tyrian Remastered:Bullet Fire A")
end
function Bullet:Heartbeat()
self:Move()
self:CheckForHit()
self:draw()
end
function Bullet:Move()
self.cycles = self.cycles - 1
self.pos.x = self.pos.x + (self.vector.x ) * self.speed
self.pos.y = self.pos.y + (self.vector.y ) * self.speed
if self.cycles < 1 or (self.pos.y >TOP-50 or self.pos.y <1) then
RemoveBullet(self.name)
else
self.pos = CorrectPosition(self.pos)
end
end
function Bullet:draw()
-- ac if its a mine use a different drawing style?
sprite(self.img, self.pos.x-worldpos.x, self.pos.y)
-- draw bullet on hud
pushMatrix()
if (string.find(self.name, "Player") ) then
stroke(110, 56, 68, 255)
else
stroke(216, 216, 216, 255)
end
ellipse(self.pos.x/ratio+HUD_LEFT, self.pos.y/ratioy+TOP, 2,2)
popMatrix()
end
function Bullet:CheckForHit()
local rbx = self.pos.x - player.width -- before
local rax = self.pos.x + player.width -- after yes, bad naming convention!
local rby = self.pos.y - player.height
local ray = self.pos.y + player.height
if (rbx < (player.screenpos.x+worldpos.x) and rax > (player.screenpos.x+worldpos.x) and
(rby < player.screenpos.y and ray > player.screenpos.y)) then
if not (string.find(self.name, "Player") ) then -- fired from enemy
player:IsHit(self.damage)
RemoveBullet(self.name)
end
end
--see if a player bullet hit anything!
if (string.find(self.name, "Player") ) then
for k,v in pairs(enemies) do
local minx = v.pos.x - v.width
local maxx = v.pos.x + v.width
local miny = v.pos.y - v.height
local maxy = v.pos.y + v.height
if (minx < self.pos.x and maxx > self.pos.x and
miny < self.pos.y and maxy > self.pos.y) then
v:IsHit(1) -- bullets do 1 pt damage.
if v.health < 1 then RemoveEnemy(v.name) end --should this be in each class?
RemoveBullet(self.name)
end
-- see if player hit a guy with a bullet.
for k,v in pairs(guys) do
local minx = v.pos.x - v.width
local maxx = v.pos.x + v.width
local miny = v.pos.y - v.height
local maxy = v.pos.y + v.height
if (minx < self.pos.x and maxx > self.pos.x and
miny < self.pos.y and maxy > self.pos.y) then
v:IsHit(self.shields) --i shot a guy. Big ouch.
RemoveBullet(self.name)
end
end
end
end
end
----
-------------------------------------------------------------------------------
-- Default options.
-- @class table
-- @name default_options
-- @field yield_frequency Determines how many iterations of a loop should be
-- executed before yielding. Set to -1 to never yield in a loop.
-- @field arrow_head_ratio Ratio of arrow head compared to its length.
-- @field arrow_head_angle Determines how pointy arrow is in radians.
-------------------------------------------------------------------------------
local default_options = {
yield_frequency = 15,
arrow_head_ratio = 0.4,
arrow_head_angle = 0.25
}
------------------------------------------------------------------------------
-- Set yield frequency.
-- @see default_options
-------------------------------------------------------------------------------
function SetYieldFreq (freq)
default_options.yield_frequency = freq
return true
end
------------------------------------------------------------------------------
-- Set arrow head ratio.
-- @see default_options
-------------------------------------------------------------------------------
function SetArrowHeadRatio (ratio)
default_options.arrow_head_ratio = ratio
return true
end
------------------------------------------------------------------------------
-- Set arrow head angle.
-- @see default_options
-------------------------------------------------------------------------------
function SetArrowHeadAngle (angle)
default_options.arrow_head_angle = angle
return true
end
-------------------------------------------------------------------------------
--- AC: draw on the canvas, codea style
-------------------------------------------------------------------------------
function WhiteboardDraw(x1,y1,x2,y2)
pushStyle()
stroke(255, 6, 0, 255)
strokeWidth(40)
line(x1,y1,x2,y2)
popStyle()
end
-------------------------------------------------------------------------------
--- Draw an arrow pointing at a provided point.
-- @param x1 x-coord of arrow head.
-- @param y1 y-coord of arrow head.
-- @param length length of arrow from head to tail.
-- @param bearing bearing arrow should point.
-- @usage draw.ArrowAt4f(0.0,0.0,10.0,math.pi*0.25)
-- @see default_options
-- @see ArrowAt3v, ArrowFrom3v, ArrowFrom4f
-------------------------------------------------------------------------------
function ArrowAt4f (x1, y1, length, bearing)
local head_ratio = default_options.arrow_head_ratio
local head_angle = default_options.arrow_head_angle
--arrow body
local angle = math.pi + bearing
local x2 = x1 + (math.sin(angle) * length)
local y2 = y1 + (math.cos(angle) * length)
WhiteboardDraw(x1, y1, x2, y2)
--arrow head left
angle = (math.pi * (1.0 - head_angle)) + bearing
x2 = x1 + (math.sin(angle) * length * head_ratio)
y2 = y1 + (math.cos(angle) * length * head_ratio)
WhiteboardDraw(x1, y1, x2, y2)
--arrow head right
angle = (math.pi * (1.0 + head_angle)) + bearing
x2 = x1 + (math.sin(angle) * length * head_ratio)
y2 = y1 + (math.cos(angle) * length * head_ratio)
WhiteboardDraw(x1, y1, x2, y2)
return true
end
-------------------------------------------------------------------------------
--- Draw an arrow pointing at a provided point.
-- @param xy1 table with the x-y coords for arrow head
-- @param length length of arrow from head to tail.
-- @param bearing bearing arrow should point.
-- @usage draw.ArrowAt3v({0.0,0.0},10.0,math.pi*0.25)
-- @see default_options
-- @see ArrowAt4f, ArrowFrom3v, ArrowFrom4f
-------------------------------------------------------------------------------
function ArrowAt3v (xy1, length, bearing)
ArrowAt4f (xy1[1], xy1[2], length, bearing)
return true
end
-------------------------------------------------------------------------------
--- Draw an arrow pointing from a provided point.
-- @param x1 x-coord of arrow tail.
-- @param y1 y-coord of arrow tail.
-- @param length length of arrow from head to tail.
-- @param bearing bearing arrow should point.
-- @usage draw.ArrowFrom4f(15.0,0.0,10.0,math.pi*0.56)
-- @see default_options
-- @see ArrowAt3v, ArrowAt4f, ArrowFrom3v
-------------------------------------------------------------------------------
function ArrowFrom4f (x1, y1, length, bearing)
local head_ratio = default_options.arrow_head_ratio
local head_angle = default_options.arrow_head_angle
--arrow body
local angle = bearing
local x2 = x1 + (math.sin(angle) * length)
local y2 = y1 + (math.cos(angle) * length)
WhiteboardDraw(x1, y1, x2, y2)
--arrow head left
x1 = x2
y1 = y2
angle = bearing - (head_angle * math.pi)
x2 = x1 - (math.sin(angle) * length * head_ratio)
y2 = y1 - (math.cos(angle) * length * head_ratio)
WhiteboardDraw(x1, y1, x2, y2)
--arrow head right
angle = bearing + (head_angle * math.pi)
x2 = x1 - (math.sin(angle) * length * head_ratio)
y2 = y1 - (math.cos(angle) * length * head_ratio)
WhiteboardDraw(x1, y1, x2, y2)
return true
end
-------------------------------------------------------------------------------
--- Draw an arrow pointing from a provided point.
-- @param xy1 x-y coord for arrow tail
-- @param length length of arrow from head to tail.
-- @param bearing bearing arrow should point.
-- @usage draw.ArrowFrom3v({15.0,0.0},10.0,math.pi*0.56)
-- @see default_options
-- @see ArrowAt3v, ArrowAt4f, ArrowFrom4f
-------------------------------------------------------------------------------
function ArrowFrom3v (xy1, length, bearing)
ArrowFrom4f (xy1[1], xy1[2], length, bearing)
return true
end
----
-- subset of the old school explode code form forums
NUMSTARS = 50
COLOURS = {
color(255, 0, 0, 125),
color(255, 227, 0, 125),
color(99, 255, 0, 125),
color(0, 140, 255, 125),
color(238, 0, 255, 125),
color(255, 156, 0, 125),
color(0, 255, 189, 125),
color(255, 0, 146, 125)
}
-- number of particles of each firework
Firework_Parts=150
Life=30
Life_Variation=100
Air_Resistance=0.1
PartSize=15
PartSize_Variation=50
Velocity=50
Velocity_Variation=100
Color_Variation=50
Explode = class()
function Explode:init()
self.fireworks={}
end
function Explode:draw()
-- draw fireworks
for k,fw in pairs(self.fireworks) do
if fw:isDead() then
self.fireworks[k] = nil
else
pushStyle()
fw:draw()
popStyle()
end
end
end
function Explode:createFirework(x,y,color)
--local pos = self:freePos(self.fireworks)
self.fireworks[1] = Firework(x,y,color)
end
function Explode:freePos(list)
return self:freePosRecursive(list,1)
end
function Explode:freePosRecursive(list,n)
if list[n] == nil then
return n
else
return self:freePosRecursive(list,n+1)
end
end
----
Firework = class()
function Firework:init(x,y,colour)
-- you can accept and set parameters here
self.p = {}
self.numParticles = Firework_Parts
for i=1,self.numParticles do
local psize = genNumber(PartSize,PartSize_Variation)
local v = vec2(math.random(-100,100),math.random(-100,100))
v = v:normalize()
v = v * genNumber(Velocity,Velocity_Variation)
local c = color(genNumber(colour.r,Color_Variation),
genNumber(colour.g,Color_Variation),
genNumber(colour.b,Color_Variation),
colour.a
)
self.p[i] = Particle(x,
y,
psize,
genNumber(Life,Life_Variation),
c,
v)
--self.p[i].sprite = Sprite:GetImage()
end
end
function Firework:draw()
local resistance = 1/(Air_Resistance + 1)
local g = vec2(Gravity.x,Gravity.y)
for i=1,self.numParticles do
local p = self.p[i]
p.x = p.x + p.v.x
p.y = p.y + p.v.y
p.v = p.v + g
p.v = p.v * resistance
local size = math.random(PartSize) * (p.lifeLeft/p.life)
p.width = size
p.height = size
p:draw()
end
end
function Firework:isDead()
for i=1,self.numParticles do
p = self.p[i]
if p.lifeLeft ~= 0 and
(p.x>0 and p.x<WIDTH and p.y>0 and p.y<HEIGHT)then
return false
end
end
return true
end
----
FlashScore = class()
-- used to display a score on the screen. builds ina delay for x secs.
function FlashScore:init(params)
-- you can accept and set parameters here
self.time = params.time or 5 --seconds
self.text = params.text or "..." -- display text
self.pos = params.pos -- where in the world to show it.
self.startTime = nil
self:start() -- auto start
end
function FlashScore:start()
self.startTime = ElapsedTime
end
-- false means its done and should be purged.
function FlashScore:active()
if self.startTime == nil then return false end
return ElapsedTime - self.startTime < self.time
end
function FlashScore:draw()
if self.startTime == nil then return end
if ElapsedTime - self.startTime < self.time then
pushStyle()
font("MyriadPro-Bold")
fontSize(16)
r=math.random(255)
g=math.random(255)
b=math.random(255)
fill(r,g,b,255)
text(self.text, self.pos.x, self.pos.y)
popStyle()
end
end
----
-- a guy. points/goal.
Guy = class()
function Guy:init(param)
assert(param.name, "All guys must have a name")
self.pos = param.pos
self.name = param.name
self.fall=1
self.isGrabbed=false --did an alien get me?
self.onShip = false --am I attached to the ship?
self.img = imgGuy
self.width = self.img.width
self.height = self.img.height
self.points = 0
self.movement = (math.random(10) -5) / 10 --determined how fast and how far.
end
function Guy:Heartbeat()
self:Move()
self:draw()
end
function Guy:Move()
if (self.isGrabbed == true or self.onShip==true) then return end -- owner will move it
if self.pos.y > 15 then
self.fall=self.fall*1.003
self.pos.y=self.pos.y - self.fall
else --we are at the ground.
if self.fall > 2 then
-- die, fell too fast
RemoveGuy(self.name)
else
self.fall = 1 --should fix this logic
self.pos.x = self.pos.x - self.movement
--AC: HACK!!! stop invaders from getting near "end" of world (0/10000)
if self.pos.x < 15 or self.pos.x > WORLDSIZE - 15 then
self.pos.x = self.pos.x - (-1 * (self.movement* 15)) --"jump" if bad to begin with
self.movement = -1 *self.movement --turn around
end
self.pos = CorrectPosition(self.pos)
end
end
end
function Guy:draw()
sprite(self.img, self.pos.x-worldpos.x, self.pos.y)
end
function Guy:IsHit()
RemoveGuy(self.name)
end
----
--helpers
demo = false --demo mode - occurs if menu is up with nothing going on.
archive = false --set to false when compiling for tests
release = false --if we are Building XCode for the iPad
debug = true
HUD_HEIGHT =75
TOP = HEIGHT - HUD_HEIGHT
HUD_WIDTH = WIDTH/2
--if (release==true) then
if ContentScaleFactor==1 then
HUD_LEFT = HUD_WIDTH/2.0 --NON-RETINA?
else
HUD_LEFT = HUD_WIDTH/1.2
end
WORLDSIZE = 10000 +WIDTH
WORLDSCREENSIZE = WORLDSIZE - WIDTH --so the world rendered
DEMODELAY = 10 --seconds before a demo starts
--used in several classes to draw on the HUD directly.
ratio = WORLDSIZE / HUD_WIDTH
ratioy = TOP / HUD_HEIGHT
function StartNewGame()
enemies={} -- all enemies
bonuses={} -- shields currently
guys = {}
bullets = {} --all bullets. players have bullet names of "player"
score = 0
hyperspace = false
lives=3
nextLife = 10000
wave = 1 -- -what level are we on?
subwave=0 --as we advance...
saucerCount = 1
--VirtualStick variables
dx=0; dy=0 --stick's delta x/y
steerpos = vec2(0,0) --redndant?
steer = vec2(0,0) --redundant?
speed = 15 -- pixels per second
worldpos = vec2(200, 0) --screen position - NOT THE SAME AS SHIP POSITION!
player=Ship() --ship has it's own screen offset so we can "turn around"
explode = Explode() -- any explosions we need
fs=FlashScore({time=5, text="", pos=vec2(100,300)}) -- as we need onscreen scores
startPause=ElapsedTime
GetHighScores()
end
function Demo(mode)
screenTime =ElapsedTime
if mode==true then
lives = 3
demo = true
StartNewGame()
wave = math.random(3) + 10 --show lots of stuff
CreateGuys() --force some guys...
CreateEnemies(wave)
game = ScreenGame()
player=Ship()
player.shields = 250
lastBullet = 0 --important! we don't fire otherwise!
GAMESTATE = GAMESTATE_PLAYING -- start the demo now
else
demo= false
ScreenTitle:init()
GAMESTATE = GAMESTATE_MENU
StartNewGame()
end
end
function CreateGuys()
guys = {} --clear any existing guys.
for x=1, 10 do
g = Guy({pos = vec2(math.random(WORLDSCREENSIZE), 25), name="Guy"..x} )
table.insert(guys, g)
end
hyperspace=false
end
-- now we have waves of enemies so les on the screen at once...
function CreateEnemies(wave)
--1/5/2013: to try to manage this better, moved CreateGuys spawning here.
if math.ceil((wave-1)/5) == (wave-1)/5 and
subwave==math.floor(wave / 3) then --subwave code from ScreenGame
CreateGuys()
end
bonuses={} --clear any that were left around
--every 5th wave a spawner is sent...
--points will double every hit, so the base here is misleading...
--we reduced damage because the ship hits too hard!
if math.ceil((wave-1)/5) == (wave-1)/5 and wave > 1 then
local i = Spawner({pos=vec2(5000, TOP-75),
img = imgSpawner, name="Spawner"..math.random(1000), cycles=10, points=25,
damage = 10, impactDamage = 5000, speed = 2 + math.random(1,wave/5),
FireSpeed = 10, health = math.ceil(4 + wave / 5), spawnCycle=120,
hudColor = color(255, 0, 0, 255),
drawparams = anim[SPAWNER]
})
table.insert(enemies, i)
else
--do the regular enemy list
num = 8 + wave
for x=1,num do
local i = Invader({pos=vec2(math.random(WORLDSCREENSIZE), TOP-50),
img = imgAlien, name="Invader"..x, cycles=240, points=150,
damage = 1, impactDamage = 5, speed = math.random() + wave/20,
drawparams = anim[3],
FireSpeed = 7})
--only mutants in space!
if hyperspace==true then i:Mutate() end
table.insert(enemies, i)
end
if wave > 5 then
for x=1, ((wave-5)/4)+1 do
local i = Star({pos=vec2(math.random(WORLDSCREENSIZE), TOP-50), img = imgStar,
name="Star"..x, speed = .2, cycles=9999, points=1000, looking=false,
damage = 1, impactDamage = 20, hudColor = color(155, 26, 139, 255),
drawparams = anim[5],
})
table.insert(enemies, i)
end
for x=1, (wave-5)/3+1 do
local i = MineLayer({pos=vec2(math.random(WORLDSCREENSIZE), TOP-50),
img = imgMineLayer,
name="MineLayer"..x, speed = .6, cycles=480, points=250, looking=false,
damage = 5, impactDamage = 10,
drawparams = anim[7],
})
table.insert(enemies, i)
end
end
saucerCount = 1 --reset JIC
if wave > 0 then
for x=1,(math.ceil(wave/5)) do
Saucer:Spawn(true) --spawn for a level. do not use for singles.
end
end
--all enemies created
end
--shields - in a seperate table now. rendered seperate from enemies
--consider making a new function?
local sv = 1 --every 5th wave, make shields stronger.
if math.ceil((wave-1)/5) == (wave-1)/5 then sv = 2 end
for x=1,(math.ceil(wave/4)+1) do
local i=Shields({pos=vec2(math.random(WORLDSIZE-1000)+250, math.random(TOP-50)+25),
img = imgShields,
name="Shields"..x, speed = .02, cycles=0, points=250, looking=false,
damage = 0, impactDamage = 0, shieldvalue=50*sv,
hudColor = color(11, 246, 2, 255),
drawparams = anim[10],
})
--this is a 69,64 sized object...size on drawparams scaled it down...
table.insert(bonuses, i)
end
end
function LoadAssets()
if (release ~= true) then
assetPrefix="Dropbox:"
else
assetPrefix ="CofenderGfx2:"
end
imgAlien = readImage(assetPrefix.."alien")
imgMutant = readImage(assetPrefix.."mutant")
imgMutantSanta = readImage(assetPrefix.."mutantII") -- special
imgGuy = readImage(assetPrefix.."guy")
imgShip = readImage(assetPrefix.."shipstill")
imgThrust = readImage(assetPrefix.."shipmove")
imgStar = readImage(assetPrefix.."star")
imgStarling = readImage(assetPrefix.."starling")
imgMineLayer = readImage(assetPrefix.."minelayer")
imgSaucer = readImage(assetPrefix.."saucer")
imgBomb = readImage(assetPrefix.."bomb")
imgShields=readImage(assetPrefix .. "ShieldBall2") --this one is altased, next isnt.
imgShieldDeflect=readImage(assetPrefix .. "ShieldBall") --render a shield
imgSpawner = readImage(assetPrefix.."Spawner")
--for title/scoring screen
assets ={ imgShip, imgGuy, imgAlien, imgMutant, imgStar, imgStarling,
imgMineLayer, imgSaucer,imgSpawner,imgShields,}
SHIP=1 GUY=2 ALIEN=3 MUTANT=4 STAR=5 STARLING=6
MINELAYER=7 SAUCER=8 SPAWNER=9 SHIELDS=10
-- 11 is the special mutant. not rendered on title screeen, just reuse mutant. same anim
anim = {
nil,
nil,
{img=assets[ALIEN], fps=10, size = vec2(30,24), frames = 5, rows = 1, row=0},
{img=assets[MUTANT], fps=10, size = vec2(30,24), frames = 5, rows = 1, row=0},
{img=assets[STAR], fps=10, size = vec2(22,22), frames = 5, rows = 1, row=0},
{img=assets[STARLING], fps=10, size = vec2(20,10), frames = 5, rows = 1, row=0},
{img=assets[MINELAYER], fps=10, size = vec2(24,18), frames = 5, rows = 1, row=0},
{img=assets[SAUCER], fps=10, size = vec2(30,16), frames = 5, rows = 1, row=0},
{img=assets[SPAWNER], fps=10, size = vec2(81,20), frames = 3, rows = 1, row=0},
{img=assets[SHIELDS], fps=10, size = vec2(32,32), frames = 5, rows = 1, row=0},
}
end
--if we go past the end of the world (10000-WIDTH, NOT 10000!) then wrap around
--non viusally.
function CorrectPosition(pos)
if pos.x>WORLDSIZE-1 then pos.x=0 end
if pos.x< 0 then pos.x=WORLDSCREENSIZE - 1 end
if pos.y > TOP-12 then pos.y = TOP-12 end
if pos.y < 10 then pos.y = 10 end
return pos
end
--Sloppy drawing - we aren't pushing or popping styles.
function DrawHUD()
rectMode(CORNER)
noFill()
noSmooth()
stroke(29, 38, 232, 255)
-- lineCapMode(SQUARE)
strokeWidth(1)
cur=vec2(pts[#pts-1]/HUD_HEIGHT, HUD_HEIGHT)
rect(HUD_LEFT, TOP, HUD_WIDTH, HUD_HEIGHT)
print(HUD_LEFT)
print(TOP)
print(HUD_WIDTH)
print(HUD_HEIGHT)
print("===")
if (hyperspace == false) then
--draw the world in the HUD
stroke(168, 168, 168, 255)
strokeWidth(1)
for x=1,#pts,2 do
cur=vec2(pts[x], pts[x+1])
if cur.x > WORLDSIZE then cur.x=WORLDSIZE end
line(prev.x/ratio+HUD_LEFT, prev.y/ratio+(TOP),
cur.x/ratio+(HUD_LEFT),cur.y/ratio+(TOP))
prev= cur
end
end
--draw the screen bracket
stroke(206, 33, 33, 255)
strokeWidth(4)
--ratio/scale
local l = (worldpos.x / WORLDSIZE) * HUD_WIDTH
local bar = ratio
--fix bar sizes
line(HUD_LEFT+l, TOP+2, HUD_LEFT+l+ratio, TOP+2)
line(HUD_LEFT+l, TOP + HUD_HEIGHT-2, HUD_LEFT+l+ratio, TOP+ HUD_HEIGHT-2)
--draw ship on HUD - consider consolidating all of these draws into the helper.
stroke(250, 211, 13, 255)
ellipse((player.screenpos.x+worldpos.x)/ratio+HUD_LEFT, player.screenpos.y/ratioy+TOP, 2,2)
if player.shields==nil then player.shields=0 end
--draw shield bar
stroke(115, 81, 38, 255)
fill(0, 0, 0, 255)
rect(HUD_LEFT -180, TOP + 20, 500 / 3, 15)
fill(172, 31, 31, 255)
rect(HUD_LEFT - 180, TOP + 20, player.shields / 3, 15)
--draw lives left
sprite(imgShip, HUD_LEFT+HUD_WIDTH+player.width/2, HEIGHT-(player.height) )
font("ArialRoundedMTBold")
fontSize(8)
text(math.floor(player.shields), HUD_LEFT - 100, TOP+10) --shields left
fontSize(20)
text("shields", 75, TOP+28)
text("x" .. lives, HUD_LEFT+HUD_WIDTH+player.width+8, HEIGHT-(player.height) )
-- bomb icon goes here
sprite(imgBomb, HUD_LEFT+HUD_WIDTH+player.width/2,
HEIGHT-player.height-imgBomb.height )
text("x" .. player.bombs,
HUD_LEFT+HUD_WIDTH+player.width+8,
HEIGHT-player.height-imgBomb.height )
text("score", 900, HEIGHT-15)
text(score, 900,HEIGHT-35)
text("wave", 60, HEIGHT-15)
text(wave, 100, HEIGHT-15)
if DEBUG then
text(math.floor(waveTime), 20,HEIGHT-15)
end
popStyle()
end
function LostLife()
lives=lives-1
bullets={} --clear all bullets!
if lives <1 then
ScreenEnd:init()
GAMESTATE=GAMESTATE_ENDED
else
ShipActive=false
--explode parameters
life=600
explode:createFirework(player.screenpos.x,player.screenpos.y,color(0,0,255,255))
player.shields= player.STARTSHIELDS
player.screenpos.y = WIDTH/2 --move to "center" again.
--and somewhere else - starlings are bad news!
worldpos.x = math.random(WORLDSCREENSIZE)
CorrectPosition(player.screenpos)
CorrectPosition(worldpos)
for k,v in pairs(guys) do
if v.onShip==true then
v:IsHit(1) --he MIGHT survive :P
else
v.pos.y = 5 --move all guys to ground
v.fall=1
v.isGrabbed=false
v.onShip = false
--AC If an enemy has a guy, he will still have them when we come back...
--but that's OK.
end
end
GAMESTATE=GAMESTATE_SHIPEXPLODE
end
end
function RemoveGuy(name)
for k,v in pairs(guys) do
if v.name == name then
--if an invader was trying to get him, mark the invader as free again
for kk,vv in pairs(enemies) do
if (vv.myHuman ~= nil) then
if (vv.myHuman.name == v.name) then
vv.myHuman = nil
vv.looking = true
end
end
end
local n = math.random(#COLOURS)
explode:createFirework(v.pos.x-worldpos.x,v.pos.y,COLOURS[n])
table.remove(guys, k)
-- if all guys are gone, we go into hyperspace
if (#guys == 0) then HyperSpace() end
return
end
end
end
function RemoveBullet(name)
for k,v in pairs(bullets) do
if v.name == name then
table.remove(bullets, k)
return
end
end
end
--Removes actors from the enemies table.
function RemoveEnemy(name)
for k,v in pairs(enemies) do
if v.name == name then
if v.health < 1 then
if v.myHuman ~= nil then v.myHuman.isGrabbed = false end
AddScore(v.points, v.pos-worldpos)
table.remove(enemies, k)
return
end
end
end
end
--if we hit a bonus (shields so far) remove them from the other table.
function RemoveBonus(name)
for k,v in pairs(bonuses) do
if v.name == name then
if v.points > 0 then --only show value if it's worth something!
AddScore(v.points, v.pos-worldpos)
end
table.remove(bonuses, k)
return
end
end
end
--determine if we made enough points for out next life.
function AddScore(points, position)
if position ~= nil then
fs=FlashScore({time=2, text=points, pos=vec2(position.x, position.y)})
fs:start()
table.insert(rewards, fs)
end
--redundant math here.HACKY!!!..should store a "next ship" value globally.
curLives = math.ceil(score/nextLife)
score = score + points
newLives = math.ceil(score/nextLife)
if newLives > curLives and curLives > 0 then
lives = lives + 1
player.bombs = player.bombs + 1
end
end
function SummarizeLevel()
--if we have sublevels, load before we close the level
if subwave>0 then
CreateEnemies(wave)
subwave = subwave - 1
return
end
--the "pause" screen is responsible for rewards
startPause = ElapsedTime
wave = wave + 1 -- AVOID INFINITE WAVE LOOP
sound(DATA, "ZgFANw45VmUwI0F/xAy+PhWSYj8l7Ds/LgBSf0VdKj8/WDt/", .7)
--give bonus points
AddScore( (#guys) * 100 , nil)
--player.shields = Ship.STARTSHIELDS --allow player to retain their shielding.
GAMESTATE = GAMESTATE_LEVELCOMPLETE
bullets={}
subwave=math.floor(wave / 3)
end
function HyperSpace()
hyperspace = true
for k,v in pairs(enemies) do
if string.find(v.name , "Invader") and v.isMutant == false then
v:Mutate()
end
end
end
function ShowHighScores()
local size = 36
GetHighScores()
if #highscores < 1 then ResetHighScores() GetHighScores() end
pushStyle()
font("ArialRoundedMTBold")
fontSize(size)
for x=1,5 do
if sorted[highscores[x].score] == nil then sorted[highscores[x].score]="***" end
text(highscores[x].score, 400, 425 - (x*size))
text(sorted[highscores[x].score], 550, 425 - (x*size))
end
popStyle()
end
function SaveHighScores()
h=JSON:encode(highscores)
saveLocalData("Cofender",h)
end
function GetHighScores()
h=readLocalData("Cofender")
if h == nil then
ResetHighScores()
end
highscores=JSON:decode(h)
sorted = {}
for k,v in pairs(highscores) do sorted[v.score] = v.player end
table.sort(highscores, function (a,b) return a.score > b.score end)
end
function ResetHighScores()
highscores = {
{score=0, player="AC"},
{score=1000, player="AC"},
{score=1250, player="AC"},
{score=1500, player="AC"},
{score=2000, player="AC"},
}
SaveHighScores()
end
--to override stick drawing! works, "self" below is VirtualStick...
function VirtualStick:draw()
if self.touchId ~= nil then
pushStyle()
ellipseMode(RADIUS)
strokeWidth(1)
stroke(48, 44, 44, 255)
noFill()
pushMatrix()
translate(self.touchStart.x, self.touchStart.y)
--ellipse(0, 0, self.radius, self.radius)
ellipse(0, 0, self.deadZoneRadius, self.deadZoneRadius)
translate(self.stickOffset.x, self.stickOffset.y)
fill(63, 66, 166, 255)
ellipse(0, 0, 25, 25)
popMatrix()
popStyle()
end
end
----
-- a creature to steal guys. turns into mutants.
Invader = class(Attacker)
function Invader:init(params)
Attacker.init(self,params)
end
function Invader:Heartbeat()
if (self.isMutant == false) then
self:GetHuman()
self:CanPromote()
end
Attacker.Heartbeat(self)
end
function Invader:Move()
if (self.isMutant == true) then
-- do mutatny stuff - make them track to player position
local dx = (math.random() - .5) * 3
local dy = (math.random() - .5) * 3
local delta = (worldpos + player.screenpos) - self.pos
if math.abs(delta.x)>WORLDSIZE/2 then delta=delta *-1 end -- faster the other way...
delta = delta + self.oldDelta*self.speed --
delta = delta:normalize()
self.pos.x = self.pos.x + (delta.x + dx) * self.speed
self.pos.y = self.pos.y + (delta.y + dy) * self.speed
self.pos = CorrectPosition(self.pos)
return
end
if (self.looking == true) then
if self.pos.y < 35 then
self.pos.x=self.pos.x-self.speed
else
self.pos.y=self.pos.y-self.speed --seek a guy
end
else
self.pos.y=self.pos.y+self.speed --try to steal a guy
end
if (self.myHuman ~= nil) then -- and move the stolen guy
self.myHuman.pos.x = self.pos.x --try to center. HACK!!
self.myHuman.pos.y = self.pos.y - self.img.height
end
self.pos = CorrectPosition(self.pos)
end
function Invader:GetHuman()
if (self.looking == true and self.myHuman == nil) then
--try to target/find a human
for k,v in pairs(guys) do
rbx = self.pos.x - 30
rax = self.pos.x + 30
rby = self.pos.y - 30
ray = self.pos.y + 30
if (rbx < v.pos.x and rax > v.pos.x and rby < v.pos.y and ray > v.pos.y) then
if v.isGrabbed == false then -- cause sometimes two invaders grab the same guy!
--grab the guy!
self.direction = -1 -- go up
sound(SOUND_POWERUP, 37637)
self.myHuman = v --so we know which guy is converted
self.looking = false
v.isGrabbed = true
end
end
end
end
end
function Invader:CanPromote()
if self.pos.y > TOP - 20 then
if self.myHuman ~= nil then
self:Mutate()
RemoveGuy(self.myHuman.name)
self.myHuman = nil
else
self.looking = true
self.direction = 1
end
end
end
function Invader:Mutate()
sound(SOUND_HIT, 39749)
self.isMutant = true
self.looking = false
self.points = 300
self.speed = self.speed * 1.5
self.img = imgMutant
self.FireSpeed = self.FireSpeed*1.5
self.maxCycles = 60
self.cycles = self.maxCycles
if math.random(10) < 2 then
self.img = imgMutantSanta --special mutant = stronger, faster
self.points = self.points * 1.5
self.speed = self.speed * 1.5 --again...on purpose
self.maxCycles= 20 --faster shooting too
self.cycles = self.maxCycles
self.FireSpeed = self.FireSpeed *1.5
self.damage = 100 -- why? seems high
self.predictive=true
end
self.d:ResetTexture(self.img)
end
--no need to override draw as we are not dong custom drawing for these guys!
--Can safely remove.
--function Invader:draw()
-- Attacker.draw(self)
--end
function Invader:touched(touch)
Attacker.touched(touch.self)
end
function Invader:Shoot()
self.cycles = self.cycles - 1
if self.cycles < 1 then
--call only one or else you get homing bullets!
local vector = (worldpos + player.screenpos) - self.pos
vector = vector:normalize()
local dist = self.pos:dist(player.screenpos + worldpos)
if (dist< 1200 and dist > 10) then --don't fire if TOO close! Stray bullets!
self.cycles = self.maxCycles --again. should make a CONST
if self.isMutant then
sound(DATA, "ZgNAJABAOmY8P15PLZloPm8lCD8AAAAARQBiWSFAOFRJDVol")
else
sound(DATA, "ZgNAJwBDE1xPITlPiEBdPW8lCD8AAAAARQBifzRAOFQiGloU")
end
b = Bullet({
pos = vec2(self.pos.x, self.pos.y),
name = "Bullet"..tostring(math.random(1000)),
vector = vector , damage = self.damage,
impactDamage = self.impactDamage, speed = self.FireSpeed,
cycles = self.maxCycles+60
})
-- ac: bullet life was assoc to two things. need a bullet life variable.
table.insert(bullets, b)
end
end
end
function Invader:IsHit(damage)
if self.myHuman ~= nil then
self.myHuman.isGrabbed = false
self.myHuman.onShip =false
self.myHuman.fall= 1.001
end
--after special checks run this to remove otherwise it's too soon.
Attacker.IsHit(self, damage)
end
VERSION = "1.0.1"
PROJECTNAME = "Cofender"
----
-- Cofender
supportedOrientations(LANDSCAPE_LEFT)
displayMode(FULLSCREEN)
--external references: AutoGist, Cider, Controllers and Utils.
--personally, I have TOO MUCH STUFF in Main :) this is a simple game heartbeat system.
function setup()
--create an instance of AutoGist
--Add this to the project that you would like to backup
--@param ProjectName -- must match your project use PROJECTNAME at the top
--@param Description of your project
--@param version number. I like using a global VERSION at the top.
--@param [true|false] true will add an update checker to the gist
--If Build is true version number is ignored and a Private gist is created for builds
--If Build is false or nil the Version checks for a Release gist update
VERSION = "1.0.2"
PROJECTNAME = "Cofender"
BUILD=false
autoGist = AutoGist(PROJECTNAME,"Cofender - a defender clone written with Codea",VERSION,false)
autoGist:backup(false)
highscores = {} --pull these from a param or a network service
-- this is the world map.
pts={0,20,1000,20,1200,30,
1500,60,1800,120,2500,20,2700,60,
4000,300,5500,60,7000,20,7200,40,7400,20,7500,30,
8000,60,8500,100,8800,60,9000,20, 11000,20}
print.redirect.off()
-- define consts
GAMESTATE_MENU = 1
GAMESTATE_PLAYING = 2
GAMESTATE_SHIPEXPLODE = 3
GAMESTATE_LEVELCOMPLETE=5
GAMESTATE_ENDED = 4
GAMESTATE_SPLASH =6
GAMESTATE_OPTIONS =7
GAMESTATE_PAUSED = 8
GAMESTATES = { ScreenTitle, ScreenGame, ScreenShipExplode, ScreenEnd,
ScreenLevelComplete, ScreenSplash, ScreenOptions , ScreenPaused }
ScreenSplash:init()
GAMESTATE = GAMESTATE_SPLASH
if (archive) then
displayMode(STANDARD)
saveCode("Cofender")
end
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
--if not retain then
background(0, 0, 0, 255)
-- end
--state machine
GAMESTATES[GAMESTATE]:draw()
end
function touched(touch)
--state machine
GAMESTATES[GAMESTATE]:touched(touch)
end
--to send keys to proper screen for processing
function keyboard(key)
GAMESTATES[GAMESTATE]:keyboard(key)
end
----
-- lays mines / bullets.
MineLayer = class(Attacker)
function MineLayer:init(params)
Attacker.init(self,params)
end
function MineLayer:Shoot()
--don't shoot anything!
end
--lays down mines (bullets) wherever it goes.
function MineLayer:Move()
Attacker.Move(self) --call base move class
--we "shoot" mines
self.cycles = self.cycles - 1
if self.cycles < 1 then
b = Bullet({
pos = vec2(self.pos.x, self.pos.y),
name = "Mine"..tostring(math.random(1000)),
vector = vec2(0,0) , damage = 100, cycles = 250,
img=readImage("Tyrian Remastered:Mine Spiked")
})
table.insert(bullets, b)
self.cycles = 125 --self.MaxCycles
end
end
----
Particle = class()
Particle.DEFAULT_OPACITY = 125
Particle.DEFAULT_ANGLE = 0
Particle.DEFAULT_MASS = 1
function Particle:init(posx,posy,size,life,colour,
velocity,mass,angle,sprite)
-- position
self.x = posx
self.y = posy
self.ox = -5
self.oy = 0
-- size
self.width = size
self.height = size
-- color
if colour == nil then
self.color = color(255, 255, 255, 255)
else
self.color = colour
end
-- velocity
self.v = velocity
-- life
self.life = life
self.lifeLeft= life
-- sprite
self.sprite = sprite
-- mass
if mass == nil then
self.mass = DEFAULT_MASS
else
self.mass = mass
end
-- angle
if angle == nil then
self.angle = self.DEFAULT_ANGLE
else
self.angle = angle
end
end
function Particle:draw()
if self.lifeLeft > 0 then
self.lifeLeft = self.lifeLeft - 1
end
if self.lifeLeft ~= 0 then
if self.sprite == nil then
fill(self.color)
ellipse(self.x,self.y,self.width,self.height)
else
pushMatrix()
translate(self.x,self.y)
rotate(self.angle)
tint(self.color)
sprite(self.sprite,0,0,self.width,self.height)
popMatrix()
end
end
end
function genNumber(number,variation)
ret = variation*0.01*number
ret = number + math.random(-ret,ret)
return ret
end
----
-- the incomplete saucer class.
Saucer = class(Attacker)
function Saucer:init(params)
Attacker.init(self,params)
self.spawn = params.spawn or false
end
function Saucer:Move()
--dont hit, just "annoy"
local delta = (worldpos + player.screenpos) - self.pos
if math.abs(delta.x)>WORLDSIZE/2 then delta=delta *-1 end -- faster the other way...
local sv=delta.x/(WIDTH)
sv=1-math.abs(sv)
--delta.x=delta.x - math.random(240)-120
delta.y = delta.y - 60 -- go underneath
delta = delta + self.oldDelta*self.speed
delta = delta:normalize()
self.pos.x = self.pos.x + (delta.x) * self.speed
self.pos.y = self.pos.y + (delta.y) * self.speed
self.pos = CorrectPosition(self.pos)
if sv >.1 then
sound(DATA, "ZgBAd2tLSjVmRmoCAAAAADaUVz0AAAAAAAB0fz9AQBZAX2RG", sv)
end
end
function Saucer:CheckForImpact()
local rbx = self.pos.x - player.width
local rax = self.pos.x + player.width
local rby = self.pos.y - player.height
local ray = self.pos.y + player.height
if (rbx < player.screenpos.x+worldpos.x and rax > player.screenpos.x+worldpos.x and
rby < player.screenpos.y and ray > player.screenpos.y) then
player:IsHit(self.impactDamage) --he's on us!
--make two new respawnable saucers since you rammed this one...and discuorage that style
local drawparams = {img = imgSaucer, fps= 10, size = vec2(31,16),
pos = self.pos, move=vec2(0, 0),
self.direction , frames = 5, rows = 1, row=0}
for x=1,2 do
local i = Saucer({pos=vec2(math.random(WORLDSIZE), TOP-50), img = imgSaucer,
name="Saucer"..math.random(1000), speed = (wave/3)+4, cycles=90, points=200,
looking=false,
spawn=true, damage= 35, impactDamage = 50, FireSpeed = 15,
drawparams=drawparams
})
table.insert(enemies, i)
end
self:IsHit(player.shields) --player damages item too!
self.points=0 -- if player bumps, no points!
--or Saucer.IsHit(self, player.shields)
RemoveEnemy(self.name)
end
end
--Global static for other functions (ScreenGame) - used to spawn a level of saucers.
-- set respawn to true for a saucer that spawns others on impact.
function Saucer:Spawn(respawn)
local k,v
local isLast = 0
for k,v in pairs(enemies) do
if (string.find(v.name, "Saucer") ) then isLast = isLast + 1 end
end
if isLast < 2 then
--spawn
for x=1,saucerCount do
local x = math.random(5000)
local y = math.random(TOP-50)
local newPos = vec2(x,y)
--AC: will make the points for a rammed saucer less...and make them slower
--but deliver MUCH MORE damage.
local drawparams = {img = imgSaucer, fps= 10, size = vec2(31,16),
pos = self.pos, move=vec2(0, 0),
self.direction , frames = 5, rows = 1, row=0}
local i = Saucer({pos=newPos, img = imgSaucer,
name="Saucer"..x, speed = (wave/3)+4, cycles=90, points=200, looking=false,
damage= 15, impactDamage =25, spawn = respawn, FireSpeed = 15,
drawparams = drawparams
})
table.insert(enemies, i)
end
if (saucerCount < wave) then
saucerCount = saucerCount + 1 -- add only if all saucers are gone
end
end
end
----
ScreenEnd = class()
function ScreenEnd:init()
endController = TapAction {
pressed = function(v)
for k,v in pairs(UI) do
v:touched(CurrentTouch)
end
end,
released = function(v)
for k,v in pairs(UI) do
v:touched(CurrentTouch)
end
if bucket < 1 or bucket > 5 then
demo=false
StartNewGame()
GAMESTATE=GAMESTATE_MENU
end
end
}
UI= {} --table of controls to render.
SetupUI()
endController:activate()
--check high scores
GetHighScores()
bucket=0
--not sure this is"right", but it's working...
for k=5,1,-1 do
if (score > highscores[k].score) then
bucket = k
end
end
end
function ScreenEnd:draw()
font("AmericanTypewriter-Bold")
fontSize(28)
fill(255, 255, 255, 255)
text("Game Over", WIDTH/2, HEIGHT/2)
if (demo==true) then
ScreenTitle:init()
GAMESTATE=GAMESTATE_MENU
end
if bucket > 0 and bucket < 6 then
if (Frame ~= nil) then
--we have controls and its library
for k,v in pairs(UI) do
v:draw()
end
else
--force entry and exit
bucket = 0 --just in case?
WriteHighScore("YOU")
end
end
if (#explode.fireworks) then explode:draw() end
end
function ScreenEnd:touched(touch)
end
--lay out controls for high score entry
function SetupUI()
if (Frame ~= nil) then
textBox1000 = TextBox('', 410, 590, 610, 650, textBox1000_Clicked)
textBox1000.fontSize = 48
textBox1000.font ="Courier"
textBox1000.background=color(75, 75, 75, 255)
textBox1000.foreground=color(256, 161, 78, 256)
textBox1000.highlight=color(166, 166, 166, 255)
button1001 = TextButton('Save Score', 450, 530, 550, 580, button1001_Clicked)
button1001.background=color(119, 158, 199, 255)
button1001.highlight=color(64, 192, 66, 255)
table.insert(UI, textBox1000)
table.insert(UI, button1001)
end
end
function textBox1000_Clicked()
--This is if the box is tapped
textBox1000.text = string.sub(textBox1000.text, 1,6)
end
function button1001_Clicked()
WriteHighScore(textBox1000.text)
end
--redundant? Maybe. but I needed to write the score in the draw loop in case cidercontrols
--were not presen
--AC: Found bug. If score is a TIE, we don't actually erase the old one...and we
--appear to ovewrite it (but I'm sure that a sorted[] rendering bug
function WriteHighScore(name)
name = string.sub(name, 1,6) -- arbitrary 6
table.insert(highscores, bucket, {score= score, player=name})
hideKeyboard()
SaveHighScores()
GetHighScores() -- this will cause a re-sort, which we need for run #2
-- reset to new game - will show High Scores.
demo=false
StartNewGame()
screenPause = 0
screenTime = ElapsedTime
ScreenTitle:init()
GAMESTATE=GAMESTATE_MENU
end
function ScreenEnd:keyboard(key)
if CCActiveTextBox then
if (key == RETURN) then
textBox1000_Clicked() -- filter to 6
-- post the score
button1001_Clicked()
else
CCActiveTextBox:acceptKey(key)
end
end
end
----
ScreenGame = class()
startPause = 0 --when a wave ends, we start a paused timer
waveTime = 0
lastBullet = 0 --last time the ship fred a demo bullet
nextSaucer = 30 --first one shows, then 10 secs for each group after.
rewards={} -- array of fs objects
-- make radius via options.
function ScreenGame:init()
vsController = VirtualStick {
radius = 95, deadZoneRadius = 10,
moved = function(v)
if (demo==true) then
Demo(false)
else
steer = v
thrust=true
end
end,
released = function(v) steer = vec2(0,0) thrust=false end
}
tapController = TapAction {
pressed = function(v)
end,
released = function(v)
if (demo==true) then
Demo(false)
end
--if we are in the game do stuff otherwise ignore
if GAMESTATE==GAMESTATE_PLAYING then
--fire bullet
local vector
if player.direction == 1 then vector=vec2(-1,0) end
if player.direction == -1 then vector=vec2(1,0) end
worldpos.y = 0 --always! Otherwise bullet offsets go bad!
b = Bullet({ pos = player.screenpos+worldpos,
name = "Player"..tostring(math.random(1000)), vector=vector,
img = readImage("Tyrian Remastered:Bullet Fire A") , cycles=30} )
b.speed = 45
table.insert(bullets,b)
sound(SOUND_SHOOT, 16568)
elseif GAMESTATE == GAMESTATE_LEVELCOMPLETE then
if (ElapsedTime - startPause > 5) then --gimme a 5 sec pause
CreateEnemies(wave)
subwave=math.floor(wave / 3)
GAMESTATE=GAMESTATE_PLAYING
waveTime = 0
startPause = 0 --reset the pause "timer"/counter
end
elseif GAMESTATE == GAMESTATE_PAUSED then
GAMESTATE=GAMESTATE_PLAYING
end
end
}
bomb = TapAction {
released = function(v)
-- make a dead zone buffer
if GAMESTATE == GAMESTATE_PLAYING then
pb=40
if v.y>HEIGHT - pb and v.x < pb then GAMESTATE = GAMESTATE_PAUSED return end
if v.y > HEIGHT/2+125 then player:Bomb() end
end
end
}
jump = TapAction {
pressed=function(v) retain=true end,
released = function(v)
retain=false
if GAMESTATE == GAMESTATE_PLAYING then
if v.y > HEIGHT/2+125 then player:HyperJump() end
end
end
}
controller = SplitScreen {
left = vsController,
right = tapController
}
buttoncontroller = SplitScreen {
left = bomb,
right = jump
}
mastercontroller =SplitScreen {
top = buttoncontroller,
bottom = controller
}
--activate the split screen controller
mastercontroller:activate()
--start the game after everything is initiailized
GAMESTATE = GAMESTATE_PLAYING
startTime = ElapsedTime ---start the clock!
subwave=math.floor(wave / 3)
CreateEnemies(wave)
--game initalized sound goes here
if (#guys==0) then HyperSpace() else hyperspace=false end
end
function ScreenGame:draw()
if GAMESTATE==GAMESTATE_PAUSED then return end
waveTime = ElapsedTime - startTime
controller:draw()
prev=vec2(0,20)
DrawHUD()
--draw time elapsed - DEBUG
if (debug==true) then text(waveTime, 50, HEIGHT-15) end
if demo == true then
if (#guys==0) then
CreateGuys()
CreateEnemies(wave)
end
-- thrust the ship and fire
if lastBullet == nil then lastBullet=0 end
--if (math.random(100) > 90) then player.direction=player.direction *-1 end
if math.ceil(waveTime*2) > lastBullet then
steer = vec2(-player.direction, (math.random(100)-50)/100 )
b = Bullet({ pos = player.screenpos+worldpos,
name = "Player"..tostring(math.random(10000)),
vector=vec2(-player.direction,0),
img = readImage("Tyrian Remastered:Bullet Fire A") , cycles=30} )
b.speed = 45
table.insert(bullets,b)
lastBullet = math.ceil(waveTime*2) + math.random(10)/10
sound(SOUND_SHOOT, 16568)
end
thrust=true --always for demo
end
if (#guys==0) then HyperSpace() else hyperspace=false end
if (hyperspace == false) then
--draw the world
strokeWidth(2)
for x=1,#pts,2 do
cur=vec2(pts[x], pts[x+1])
line(prev.x-worldpos.x, prev.y,cur.x-worldpos.x, cur.y)
prev= cur
end
end
stroke(29, 38, 232, 255)
strokeWidth(9)
line(0,TOP-4,WIDTH+9,TOP-4)
player:Heartbeat()
self:touched(CurrentTouch)
--if the game is running and the ship isn't destroyed, cycle the entities
if (ShipActive) then
-- these guys have to be their own class...they have logic
--render enemies
for k,v in pairs(enemies) do
v:Heartbeat()
end
--render bullets
for k,v in pairs(bullets) do
v:Heartbeat()
end
--render rewards
for k,v in pairs(bonuses) do
v:Heartbeat()
end
end
-- ALWAYS show the explosion and guys
for k,v in pairs(guys) do
v:Heartbeat()
end
if (#explode.fireworks) then explode:draw() end
--ac hack! see if we are an inactive ship AND we are out of fireworks.
if (#explode.fireworks == 0 and ShipActive==false) then
ShipActive=true -- reactivate and start new ship.
end
--if there is an fs, show it.
for k,v in pairs(rewards) do
v:draw()
end
--it's been a while...let's encorage the player to finish!
--AC: decided to only span one - respawnable saucer.
--if we have sublevels, load before we close the level
if (waveTime > nextSaucer) then
if subwave>0 then
CreateEnemies(wave)
subwave = subwave - 1
else
Saucer:Spawn(true)
end
startTime = ElapsedTime
waveTime = 0
--nextSaucer = 30+wave*1.5
end
if (#enemies<1) then
SummarizeLevel()
end
end
function ScreenGame:touched(touch)
--steerpos = steerpos + steer*speed*DeltaTime
steerpos = steer * 18
--move world
-- if we are moving, correct orientation of shot
if steer.x < 0 and player.direction == -1 then
--steerpos = vec2(0, steer.y)
player.direction = 1
end
if steer.x > 0 and player.direction == 1 then
--steerpos = vec2(0, steer.y)
player.direction = -1
end
worldpos.x=worldpos.x + steerpos.x --player.direction
--finally correct for ship movement
-- logic needs rethinking...
if player.direction==1 then
if player.screenpos.x < WIDTH-400 then
if thrust == false then player.screenpos.x = player.screenpos.x + 30 end
else
player.screenpos.x = player.screenpos.x + steerpos.x
end
end
if player.direction==-1 then
if player.screenpos.x >= 400 then
if thrust==false then player.screenpos.x = player.screenpos.x - 30 end
else
player.screenpos.x = player.screenpos.x + steerpos.x
end
end
player.screenpos.y = player.screenpos.y + steerpos.y
if worldpos.x > WORLDSCREENSIZE then worldpos.x = 1 end
if worldpos.x < 1 then worldpos.x = WORLDSCREENSIZE-1 end
worldpos = CorrectPosition(worldpos)
player.screenpos = CorrectPosition(player.screenpos)
--slow down forward momentum
--if (thrust == false) then steerpos.x=steerpos.x * .99 end
if steerpos.x > 35 then steerpos.x = 35 end
if steerpos.y > 10 then steerpos.y = 10 end
end
----
ScreenLevelComplete = class()
function ScreenLevelComplete:init()
end
function ScreenLevelComplete:draw()
if (demo==true) then
GAMESTATE=GAMESTATE_MENU
end
DrawHUD()
waveTime = 0
stroke(255, 255, 255, 255)
local w = 0
local h = 0
-- wave got incremented already on the way in.
text("Wave " .. wave -1 .. " completed", WIDTH/2, HEIGHT/2)
--render bonus icons
if (#guys) then
for k,v in pairs(guys) do
w = v.width + 5
h = v.height
local left = WIDTH/2 + (k*w) - 80 --AC: 80 HAACK!!!
sprite(v.img, left, HEIGHT/2 - h - 5) --5 px buffer
--we really need a ResetGuy() function.
v.onShip = false
v.isGrabbed = false
v.pos.y = 5
end
--only show this if we have guys. Hyperspace has no bonus.
text("x 100", WIDTH/2 + (#guys * w),HEIGHT/2 - h - 5) --5 px buffer
end
if (ElapsedTime - startPause > 5) then --gimme a 5 sec pause
CreateEnemies(wave)
GAMESTATE=GAMESTATE_PLAYING
waveTime = 0
startPause = 0 --reset the pause "timer"/counter
end
end
----
--to be used later radius osma good use here...
ScreenOptions = class()
function ScreenOptions:init()
endController = TapAction {
pressed = function(v)
for k,v in pairs(UI) do
v:touched(CurrentTouch)
end
end,
released = function(v)
for k,v in pairs(UI) do
v:touched(CurrentTouch)
end
end
}
UI= {} --table of controls to render.
SetupOptionsUI()
endController:activate()
end
function ScreenOptions:draw()
font("AmericanTypewriter-Bold")
fontSize(28)
fill(255, 255, 255, 255)
text("Options", WIDTH/2, HEIGHT/2)
for k,v in pairs(UI) do
v:draw()
end
end
function ScreenOptions:touched(touch)
end
--lay out controls for high score entry
function SetupOptionsUI()
if (Frame ~= nil) then
textBox1001 = TextBox('', 410, 590, 610, 650, textBox1001_Clicked)
textBox1001.fontSize = 48
textBox1001.font ="Courier"
textBox1001.background=color(75, 75, 75, 255)
textBox1001.foreground=color(256, 161, 78, 256)
textBox1001.highlight=color(166, 166, 166, 255)
button1002 = TextButton('Return', 450, 530, 550, 580, button1002_Clicked)
button1002.background=color(119, 158, 199, 255)
button1002.highlight=color(64, 192, 66, 255)
table.insert(UI, textBox1001)
table.insert(UI, button1002)
end
end
function button1002_Clicked()
if textBox1001.text ~= nil then
wave = math.ceil(tonumber(textBox1001.text))
end
hideKeyboard()
player:Respawn()
player.shields=500
game = ScreenGame()
GAMESTATE = GAMESTATE_PLAYING
end
function textBox1001_Clicked()
--This is if the box is tapped
textBox1001.text = string.sub(textBox1001.text, 1,6)
end
function ScreenOptions:keyboard(key)
if CCActiveTextBox then
CCActiveTextBox:acceptKey(key)
--AC: HACK!!!
if (key == RETURN) then
textBox1001_Clicked() -- filter to 6
button1002_Clicked()
end
end
end
----
ScreenPaused = class()
function ScreenPaused:init(x)
end
function ScreenPaused:draw()
font("AmericanTypewriter-Bold")
fontSize(28)
fill(255, 255, 255, 255)
text("Paused", WIDTH/2, HEIGHT/2)
end
-- fire button is still working...not sure why. use it!
function ScreenPaused:touched(touch)
GAMESTATE=GAMESTATE_PLAYING
end
----
ScreenShipExplode = class()
function ScreenShipExplode:init()
end
function ScreenShipExplode:draw()
if self.startTime == nil then self.startTime = ElapsedTime end
if ElapsedTime - self.startTime < 1.5 then
font("AmericanTypewriter-Bold")
fontSize(28)
text("SHIELDS FAILED - SHIP LOST", WIDTH/2, HEIGHT/2)
if (#explode.fireworks) then explode:draw() end
else
self.startTime = nil
player:Respawn() --sets some basic vars in the ship
GAMESTATE=GAMESTATE_PLAYING
end
end
----
ScreenSplash = class()
function ScreenSplash:init()
-- you can accept and set parameters here
self.source ={ 1 }
self.dest ={255 }
tween(250, self.source, self.dest, 'linear', finish) --
self.start=ElapsedTime
LoadAssets()
StartNewGame()
splashController = TapAction {
pressed = function(v)
if v.y < 200 then
openURL("http://UpTwoLate.blogspot.com")
else
ScreenOptions:init() --when we get there, be working!
GAMESTATE=GAMESTATE_OPTIONS
--finish()
end
end,
released = function(v)
end
}
splashController:activate()
end
function ScreenSplash:draw()
-- This sets a dark background color
if self.source==nil then return end -- hack!!!
background(0, 0, 0, 255)
-- This sets the line thickness
strokeWidth(5)
-- Do your drawing here
ArrowAt4f(325, HEIGHT-40,HEIGHT/2 - 60,math.pi*0)
ArrowAt4f(652, HEIGHT-40,HEIGHT/2 - 60,math.pi*0)
fontSize(384)
fill(50, 137, 31, 255)
text("L 8", WIDTH/2,HEIGHT/2-192)
fontSize(36)
local clr=self.source[1]
fill(clr,clr,clr,255)
text("Up 2 Late Productions presents", WIDTH/2, HEIGHT/2 - 20)
tween.update(ElapsedTime-self.start)
end
function finish()
ScreenTitle:init()
GAMESTATE = GAMESTATE_MENU
end
----
ScreenTitle = class()
function ScreenTitle:init()
screenTime = ElapsedTime
titleController = TapAction {
pressed = function(v)
--see if we touched our button? HACK!
--[[ if v.x < 50 then
ScreenOptions:init()
GAMESTATE=GAMESTATE_OPTIONS
end ]]
end,
released = function(v)
StartNewGame() -- ac: hack! reloads alot of stuff!
player:Respawn()
game = ScreenGame()
end
}
titleController:activate()
--setup sound - could use a wav instead...for now, cheesy sound effect layering.
sound(DATA, "ZgBAJgA7eARXIklozNLQvRkigzx4M90+QABqfUJBQDxwTA5a",.2)
sound(DATA, "ZgBADwA/Nxd1fy4TbR+OvO0fWj/AuCY9TABZfz9AQDw3YHxi",.4)
sound(DATA, "ZgBAFwBxXUJAKi0R2QYBvgAAgD8EfQW/YQAjRkVoP1BqV1cm",.7)
self:MakeActors()
end
function ScreenTitle:draw()
pushStyle()
font("AmericanTypewriter-Bold")
fontSize(48)
r=math.random(255)
g=math.random(255)
b=math.random(255)
fill( r,g,b,255)
text("COFENDER", WIDTH/2, HEIGHT/2 + 150)
ShowHighScores()
fontSize(28)
fill(168, 172, 174, 255)
text("Touch to play", WIDTH/2, 100)
-- add score and text stuff to describe objects?
font("Copperplate")
fontSize(24)
for k,v in pairs(enemies) do
v:draw()
-- sprite(assets[vx], vx*spacer, HEIGHT-75)
text(v.points, v.pos.x, v.pos.y-50)
end
popStyle()
--set up a demo mode - this code should be moved elsewhere, I think
if (ElapsedTime - screenTime) > DEMODELAY then
Demo(true)
end
end
function ScreenTitle:touched(touch)
end
function ScreenTitle:MakeActors()
--
enemies={}
--make one of each to display on the title screen
scores = { "YOU", "Defend" ,150,300, 500, 250, 200, 250, "Spawn", "Shields",}
spacer = 90
worldpos = vec2(0,0)
for x=1,#assets do
local actor = Attacker({pos=vec2(x*spacer, HEIGHT-75),
img = assets[x], name="Actor"..x, points=scores[x],
drawparams = anim[x]})
table.insert(enemies, actor)
end
end
----
Shields = class(Attacker)
function Shields:init(params)
Attacker.init(self,params)
assert(params.name, "All Shields must have a name")
self.pos = params.pos
self.name = params.name
self.img = params.img
self.points = params.points or 0
self.movement = (math.random(10) -5) / 10 --determines how fast and how far.
self.shieldvalue=params.shieldvalue or 10
self.speed=params.speed or 0
end
function Shields:Heartbeat()
self:Move()
self:CheckForImpact()
self:draw()
end
function Shields:Move()
self.pos.x = self.pos.x - self.speed
--self.pos.y = self.pos.y + self.speed --later?
self.pos = CorrectPosition(self.pos)
self.shieldvalue = self.shieldvalue - .05 --shields fade with time
if self.shieldvalue < 1 then
self.points = 0 --poof!
RemoveBonus(self.name)
end
end
--we shot it? So we get the bonus.
--however, bullets only check to see if we hit enemies (today) so this code
--is not hit.
function Shields:IsHit(damage)
RemoveBonus(self.name)
end
function Shields:CheckForImpact()
local rbx = self.pos.x - player.width
local rax = self.pos.x + player.width
local rby = self.pos.y - player.height
local ray = self.pos.y + player.height
if (rbx < player.screenpos.x+worldpos.x and rax > player.screenpos.x+worldpos.x and
rby < player.screenpos.y and ray > player.screenpos.y) then
player.shields = player.shields +self.shieldvalue
if player.shields > player.MAXSHIELDS then
player.shields=player.MAXSHIELDS
end
RemoveBonus(self.name)
end
end
----
-- the player's ship.
SHIELDCOLOR = {
color(255, 0, 0, 255),
color(205, 195, 118, 255),
color(39, 182, 20, 255)
}
Ship = class()
function Ship:init(params)
self.STARTSHIELDS=10
self.MAXSHIELDS=500
--the ship position is on the screen. The ship has real-world coords, but they
--are additional to the "global" position.
self.screenpos = vec2(400,HEIGHT/2) --offset from global
self.direction = -1 --go "foward"
self.img=imgShip
self.width = self.img.width
self.height = self.img.height
ShipActive=true
self.lastSoundTime = ElapsedTime
self.shields = self.STARTSHIELDS
self.bombs = 3
self.immuneTime = ElapsedTime --set to nil after 2 secs
self.immuneFlag = true --setting something to nil during draw fails, so i used this flag.
self.immuneLength = 3 --seconds
end
function Ship:draw()
pushStyle()
if self.immuneFlag then
local tm = ElapsedTime - self.immuneTime
local tintratio = 255 / self.immuneLength --should use the tween library?
tint(tm*tintratio, tm*tintratio, tm*tintratio, 255) --just got here
if (tm > self.immuneLength) then self.immuneFlag = false end
else
tint(255, 255, 255, 255)
end
--ship rotation code - translate!
pushMatrix()
translate(self.screenpos.x, self.screenpos.y)
if (self.direction == -1) then rotate(180,0,1,0) end
--if (self.direction == 1) then rotate(0,0,1,0) end
if (thrust) then
self.img = imgThrust
else
self.img= imgShip
end
sprite(self.img, 0,0)
popMatrix()
popStyle()
end
function Ship:Heartbeat()
self:draw()
self:CheckForGuys()
self:MoveGuys()
self:DropGuys()
self:PlaySounds()
end
--used to play thrust sound every tench of a second. avoids buffer overfilling.
function Ship:PlaySounds()
if ElapsedTime - self.lastSoundTime > .1 and thrust == true then
sound(DATA, "ZgNADgJBQkFHQ0BAAAAAAMK++z4tmeg9NABAf0JAQEAVO0BB", 0.3)
self.lastSoundTime = ElapsedTime
end
end
function Ship:CheckForGuys()
--see if we "caught" anyone
for k,v in pairs(guys) do
if (v.pos.y > 15 and v.onShip == false and v.isGrabbed == false) then
local minx = v.pos.x - v.width - self.width/2
local maxx = v.pos.x + v.width + self.width /2
local miny = v.pos.y - v.height - self.height/2
local maxy = v.pos.y + v.height + self.height /2
if (minx < self.screenpos.x+worldpos.x and maxx > self.screenpos.x+worldpos.x and
miny < self.screenpos.y+worldpos.y and maxy > self.screenpos.y+worldpos.y) then
--i got him!
--v.isGrabbed=false
v.onShip = true
-- if we make an difficulty setting, play with this idea more
player.shields = player.shields + 1
sound(DATA, "ZgFAHgBdT0YyNBkYI575PJ2r3j4l7Ls+UgAqdkcqLEoRYnRU")
AddScore (500, self.screenpos)
end
end
end
end
function Ship:MoveGuys()
for k,v in pairs(guys) do
if (v.onShip == true) then
v.pos.y=self.screenpos.y+worldpos.y-self.height
v.pos.x=self.screenpos.x+worldpos.x
--flying guys increase / repair shields
if self.shields <self.MAXSHIELDS then
self.shields = self.shields + .05
--AC: Idea: allow a guy to "fall off" if we are repairing the ship
--FAIL: we end up with LOTS of bonus points as it's easy to recatch the guy..
--if math.random(100) < 50 then
-- sound(SOUND_JUMP, 6826) -- fix later
-- v.pos.y = v.pos.y - 40
-- v.pos.x = v.pos.x - 75*player.direction
-- v.onShip = false
--end
end
end
end
end
function Ship:DropGuys()
for k,v in pairs(guys) do
if self.screenpos.y < 25 and v.onShip == true then
v.onShip = false
v.isGrabbed=false
AddScore (500, self.screenpos) --looping!
sound(SOUND_RANDOM, 34174)
end
end
end
function Ship:touched(touch)
--ac: make this the bomb feature later? Or something special? unused.
--self.bomb = self.bomb + 1 --free bomb!
Ship:Bomb()
end
--called whenever the ship is created/spawned.
function Ship:Respawn()
self.immuneFlag = true
self.immuneTime = ElapsedTime --for the timer
sound(DATA, "ZgBAJQpSRkRCVzdAAAAAAG/rFD+IQN09JABRX1hSQEAZVUBI")
end
function Ship:IsHit(dmg)
if self.immuneFlag == true then return end --ignore any damage.
--blow up...if we have specials, like shields, decrement them
--and show shields graphics... that makes the game Stargate, though.
self.shields = self.shields - dmg
if self.shields< 0 then
local n = math.random(#COLOURS)
explode:createFirework(self.screenpos.x,self.screenpos.y,COLOURS[n])
LostLife()
else
--shields holding, captain!
pushStyle()
tint(color(255,255,255,255))
stroke(147, 125, 27, 255)
local s = math.ceil(self.shields/(self.MAXSHIELDS/3))
if s== 0 then s=1 end --hack!!!
clr = SHIELDCOLOR[s]
fill(clr)
strokeWidth(2)
ellipse(self.screenpos.x, self.screenpos.y, self.width*3,self.height * 3)
pushStyle()
tint(clr)
sprite(imgShieldDeflect, self.screenpos.x, self.screenpos.y, self.width*3, self.height*3)
popStyle()
end
sound(SOUND_EXPLODE, 44445)
end
function Ship:HyperJump()
retain=true
--any jump has a risk of death
if math.random() < .1 then
self:IsHit(9999) --guaranteed dead
else
--jump there...objects may already be in the place you go to, though!
-- this explode is solely visual!
explode:createFirework(self.screenpos.x,self.screenpos.y,color(0,0,255,255))
worldpos.x = math.random(9000)
--worldpos.y = 0 --NEVER ALTER WORLDPOS.Y! leave it at zero!
self.screenpos.y = math.random(HEIGHT-HUD_HEIGHT)
CorrectPosition(worldpos)
CorrectPosition(self.screenpos)
sound(DATA, "ZgNADwAqNlQ3eH9PAAAAAJs2Oz/77lc/UABhRVoZCFRGH1oR")
end
end
function Ship:Bomb()
-- see if we have any
if self.bombs <1 then return end
-- get worldpos extents
local min =worldpos.x -- making a copy otherwise its by reference...?
local max = min + WIDTH
toRemove = {}
sound(DATA, "ZgNADwAqNlQ3eH9PAAAAAJs2Oz/77lc/UABhRVoZCFRGH1oR",.7)
for k,v in pairs(enemies) do
if v.pos.x > min and v.pos.x < max then
v:IsHit(self.shields) --smart bombs do 100 damage?!
table.insert(toRemove, v)
end
end
--optimize - make a single explosion
sound(DATA, "ZgNAPgAwDks+a39AAAAAAA2+VD+dq14+fwBAf0BSFEI0R0Ap")
explode:createFirework(self.screenpos.x, self.screenpos.y, color(243, 20, 20, 255))
self.bombs = self.bombs - 1
--AC: Moved this to the bottom as inlie it seemed to be failing to operte correctly.
--Note that this smart bomb will eliminate ANYTHING, so special enemies are "just gone"
--when this is used.
for k,v in pairs(toRemove) do
if v.health < 1 then RemoveEnemy(v.name) end
end
end
----
-- Spawner lays enemy mutants. only appears every 5th wave.
Spawner = class(Attacker)
function Spawner:init(params)
Attacker.init(self,params)
self.health=params.health or 2 -- times i have to be hit
self.spawnCycle = params.spawnCycle or 20
self.maxSpawnCycle = self.spawnCycle
self.predictive=true
end
function Spawner:Move()
self.pos.x = self.pos.x + self.speed
--we "spwan" ccasionally
self.spawnCycle = self.spawnCycle - 1
if self.spawnCycle < 1 then
self:CreateSpawn(wave/5)
self.spawnCycle = self.maxSpawnCycle
end
self.pos = CorrectPosition(self.pos)
end
function Spawner:IsHit(damage)
if self.health > 0 then
self.health = self.health - damage --1
--some special sound here
sound(DATA, "ZgBAVRc9HhlAGWVdAAAAAEEE6j6pabM+ZQAzfFQ9QX9EUjwB")
self.points = self.points *2
self.FireSpeed = self.FireSpeed + 4
AddScore(self.points, self.pos-worldpos)
--create a shield, too!
local i=Shields({pos=vec2(self.pos.x-math.random(50), self.pos.y-math.random(50)),
img = imgShields,
name="Shields"..math.random(9999), speed = .7, cycles=0, points=25,
looking=false,
damage = 0, impactDamage = 0, shieldvalue=50,
hudColor = color(11, 246, 2, 255), drawparams =anim[10]
})
--this is a 69,64 sized object...size=on drawparams scaled it down...
table.insert(bonuses, i)
end
end
function Spawner:CheckForImpact()
local rbx = self.pos.x - player.width
local rax = self.pos.x + player.width
local rby = self.pos.y - player.height
local ray = self.pos.y + player.height
if (rbx < player.screenpos.x+worldpos.x and rax > player.screenpos.x+worldpos.x and
rby < player.screenpos.y and ray > player.screenpos.y) then
player:IsHit(self.impactDamage)
--self:IsHit() --we DO NOT allow the player to impact the ship, instant death
end
end
function Spawner:CreateSpawn(num)
if (#enemies > 50) then --AC: 100 is arbitrary and untested
self:Shoot() --get extra shooting turn!
self:Shoot() --get extra shooting turn #2!
return
end --we have too many to spawn!
for x=1,num do
local i = Invader({
pos=vec2(self.pos.x + math.random(250)-125, self.pos.y + math.random(250) - 125),
img = imgAlien, name="Invader"..x..math.random(9999999), cycles=240, points=0,
damage = 1, impactDamage = 5, speed = math.random() + wave/20,
FireSpeed = 7, drawparams=anim[MUTANT]
})
i:Mutate()
table.insert(enemies, i)
end
end
----
-- the star, which when shot opens up into starlings.
Star = class(Attacker)
function Star:Shoot()
--
end
--spawn if hit ever.
--Stars have 1 health so this code fires when hit, won't repeat.
function Star:IsHit(damage)
Attacker.IsHit(self,damage)
if self.health < 1 then
self:CreateStarlings(10)
end
end
-- make the starlings.
function Star:CreateStarlings(num)
for x=1,num do
i = Attacker({
pos=vec2(self.pos.x + math.random(80)-40, self.pos.y + math.random(80) - 40),
img = imgStarling, name="Starling"..x,
speed = math.random (2)+6,
cycles=50+math.random(2,50),
points=500, looking=false, impactDamage = 5,
FireSpeed = 20,
drawparams = anim[STARLING],
})
table.insert(enemies, i)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment