--# Animation
Animation = class()
function Animation:init(effect, pos, loop, speed, size, alpha)
self.pos = vec2(pos.x, pos.y)
self.img = "Dropbox:" .. effect
self.timer = 0
self.loop = loop
self.speed = speed or .1
self.scale = size or 1
self.alpha = alpha or 1
self.rot = math.random()*math.pi*2
self.w, self.h = spriteSize(self.img)
self.size = 1/math.floor(self.w/64)
self.mdl = mesh()
self.id = self.mdl:addRect(0, 0, 64, 64, self.rot)
self.mdl.texture = self.img
self.mdl:setRectTex(self.id, 0, 0, self.size, 1)
self.mdl:setRectColor(self.id, color(255, 255, 255, self.alpha*255))
self.offset = 0
self.done = false
end
function Animation:update()
self.timer = self.timer + DeltaTime
if self.timer > self.speed then
self.timer = 0
self.offset = self.offset + self.size
if self.offset > 1 then
self.done = true
else
self.mdl:setRectTex(self.id, self.offset, 0, self.size, 1)
end
end
end
function Animation:draw(vec)
local pos = self.pos - vec
pushMatrix()
translate(pos.x, pos.y)
-- rotate(ElapsedTime*360)
-- tint(255, 255, 255, self.timer*255*self.alpha)
scale(self.scale)
self.mdl:draw()
-- sprite(self.img, 0, 0, self.size)
popMatrix()
end
--# Colors
Colors = {}
local red = { r=255, g=0, b=0 }
local green = { r=0, g=255, b=0 }
local yellow = { r=255, g=255, b=0 }
local pink = { r=255, g=0, b=255 }
local blue = { r=0, g=0, b=255 }
local turquoise = { r=0, g=255, b=255 }
local greenyello = { r=127, g=255, b=0 }
local violet = { r=127, g=0, b=255 }
local lightblue = { r=0, g=127, b=255 }
local lightgreen = { r=0, g=255, b=127 }
local orange = { r=255, g=127, b=0 }
table.insert(Colors, turquoise)
table.insert(Colors, orange)
table.insert(Colors, green)
table.insert(Colors, yellow)
table.insert(Colors, pink)
table.insert(Colors, blue)
table.insert(Colors, red)
table.insert(Colors, greenyellow)
table.insert(Colors, violet)
table.insert(Colors, lightblue)
table.insert(Colors, lightgreen)
--# Main
-- Fight
supportedOrientations(LANDSCAPE_ANY)
displayMode(FULLSCREEN)
function setup()
showEditor = true--false
screen = "editor"
noSmooth()
fill(255)
font("Copperplate-Bold")
scene = nil
editor = Editor()
world = World(12, 9)
origin = {}
time, fps, cnt, lastFps = 0, 0, 0, 0
-- editor.editMode = false
strokeWidth(2)
stroke(0, 0, 0, 255)
assetList("Dropbox", TEXT)
touches = {}
nbTouches = 0
if not showEditor then
scene = Scene()
music("A Hero's Quest:Battle", true, .3)
end
end
function draw()
background(240, 240, 240, 255)
sprite("Dropbox:bg", WIDTH/2,HEIGHT/2, WIDTH, HEIGHT)
if showEditor and editor.editMode then
editor:draw()
else
scene:update()
scene:draw()
-- cleanup obselete objects
if scene.bulletIndex then
table.remove(scene.bullets, scene.bulletIndex)
end
if scene.animIndex then
table.remove(scene.animations, scene.animIndex)
end
if showEditor then
editor.playButton.img = "Dropbox:edit"
editor.playButton:draw()
end
scene.p1:drawGUI()
scene.p2:drawGUI()
end
fontSize(17)
fill(255)
time = time + DeltaTime
cnt = cnt + 1
fps = fps + 1/DeltaTime
if time > .5 then
lastFps = math.floor(10*fps/cnt)/10
time = 0
cnt = 0
fps = 0
end
text(lastFps, WIDTH-20, 10)
-- collectgarbage()
end
function touched(touch)
-- allows multitouch
if touch.state == ENDED then
touches[touch.id] = nil
nbTouches = nbTouches - 1
else
if touch.state == BEGAN then
nbTouches = nbTouches + 1
end
touches[touch.id] = touch
end
if showEditor and editor.editMode then
editor:touched(touch)
else
if showEditor and touch.state == ENDED then
editor.playButton:touched(touch)
end
scene:touched(touch)
end
end
--# Editor
Editor = class()
function Editor:init()
self.buttons = {}
self.showMenu = false
self.showTiles = false
self.showPickups = false
self.showDecals = false
self.showGrid = false
self.saved = true
self.activeButton = "move"
self.scrolling = true
self.editMode = true
self.tile = Tile()
self.saveSlot = 1
-- setup buttons
table.insert(self.buttons, Button("Dropbox:load", 50*(#self.buttons+1), HEIGHT - 50, "load"))
table.insert(self.buttons, Button("Dropbox:save", 60*(#self.buttons+1), HEIGHT - 50, "save"))
table.insert(self.buttons, Button("Dropbox:move", 74*(#self.buttons+1), HEIGHT - 50, "move"))
self.tileButton = Button("Dropbox:tiles_1", 74*(#self.buttons+1), HEIGHT - 50, "tile")
table.insert(self.buttons, self.tileButton)
self.decalButton = Button("Dropbox:decals", 74*(#self.buttons+1), HEIGHT - 50, "decal")
table.insert(self.buttons, self.decalButton)
table.insert(self.buttons, Button("Dropbox:showgrid_on", 74*(#self.buttons+1), HEIGHT - 50, "grid"))
self.pickupButton = Button("Dropbox:pickups", 74*(#self.buttons+1), HEIGHT - 50, "pickup")
table.insert(self.buttons, self.pickupButton)
table.insert(self.buttons, Button("Dropbox:portal", 74*(#self.buttons+1), HEIGHT - 50, "portal"))
self.playButton = Button("Dropbox:play", WIDTH - 50, HEIGHT - 50, "play")
table.insert(self.buttons, self.playButton)
-- portal specific
self.portalA = { px=nil, py=nil, dx=nil, dy=nil }
self.portalB = { px=nil, py=nil, dx=nil, dy=nil }
self.nbPortals = 0
end
-- either adds an opening portal or a closing (and then links them together)
-- temporary until savePortal() is called
function Editor:addPortal(x, y)
if not self.portalA.px then
self.portalA.px = x
self.portalA.py = y
return self.portalA, false
else
self.portalA.dx = x
self.portalA.dy = y
self.portalB.px = x
self.portalB.py = y
self.portalB.dx = self.portalA.px
self.portalB.dy = self.portalA.py
return self.portalB, true
end
end
-- removes linked portals
function Editor:removePortal(tile)
local px, py = tile.portal.px, tile.portal.py
local dx, dy = tile.portal.dx, tile.portal.dy
if px then
world.map[px][py].portal = nil
end
if dx then
world.map[dx][dy].portal = nil
end
end
-- actually adds the portals to the map
function Editor:savePortal(portal, tile)
self.nbPortals = self.nbPortals + 1
if self.nbPortals > #Colors then
self.nbPortals = 1
end
local px, py = portal.dx, portal.dy
local dx, dy = portal.px, portal.py
self.portalA = { px=nil, py=nil, dx=nil, dy=nil }
self.portalB = { px=nil, py=nil, dx=nil, dy=nil }
if px == dx and py == dy then
self:removePortal(tile)
return
end
local source = world.map[px][py]
local val = Colors[self.nbPortals]
local r, g, b = val.r, val.g, val.b
source.portal = { px=px, py=py, dx=dx, dy=dy, r=r, g=g, b=b }
tile.portal = { px=dx, py=dy, dx=px, dy=py, r=r, g=g, b=b }
end
-- draws environments tileset
function Editor:drawTiles()
local txt = ""
rectMode(CENTER)
resetMatrix()
textWrapWidth(96)
textAlign(CENTER)
if self.tile.solid then
fill(255, 0, 0, 194)
txt = "Tile will act as wall"
else
fill(0, 255, 38, 176)
txt = "Tile can be walked on"
end
rect(128, HEIGHT/2, 128, 128)
stroke(127, 127, 127, 255)
fill(0, 0, 0, 160)
rect(WIDTH/2, HEIGHT/2, 518, 518)
fill(255, 255, 255, 160)
text(txt, 128, HEIGHT/2)
rectMode(CORNER)
sprite("Dropbox:tiles_1", WIDTH/2, HEIGHT/2, 512, 512)
end
-- draws pickups tileset
function Editor:drawPickups()
rectMode(CENTER)
resetMatrix()
stroke(127, 127, 127, 255)
fill(0, 0, 0, 160)
sprite("Dropbox:erase", 128, HEIGHT/2, 128, 128)
rect(WIDTH/2, HEIGHT/2, 518, 518)
rectMode(CORNER)
sprite("Dropbox:pickups", WIDTH/2, HEIGHT/2, 512, 512)
end
-- draws decals tileset
function Editor:drawDecals()
rectMode(CENTER)
resetMatrix()
stroke(127, 127, 127, 255)
fill(127, 127, 127, 160)
sprite("Dropbox:erase", 128, HEIGHT/2, 128, 128)
rect(WIDTH/2, HEIGHT/2, 518, 518)
rectMode(CORNER)
sprite("Dropbox:decals", WIDTH/2, HEIGHT/2, 512, 512)
end
-- draws the Load menu
function Editor:drawLoad()
rectMode(CENTER)
resetMatrix()
fill(0, 0, 0, 160)
rect(WIDTH/2, HEIGHT/2, 512, 512)
rectMode(CORNER)
sprite("Dropbox:loadmenu", WIDTH/2, HEIGHT/2, 512, 512)
local L = WIDTH/2 - 208
local W = 400
fill(255, 0, 0, 118)
rectMode(CORNER)
if self.saveSlot == 1 then
rect(L, HEIGHT/2 + 143, 416, 65)
end
if self.saveSlot == 2 then
rect(L, HEIGHT/2 + 55, 416, 65)
end
if self.saveSlot == 3 then
rect(L, HEIGHT/2 - 32, 416, 65)
end
if self.saveSlot == 4 then
rect(L, HEIGHT/2 - 121, 416, 65)
end
if self.saveSlot == 5 then
rect(L, HEIGHT/2 - 209, 416, 65)
end
end
-- draws the Save menu
function Editor:drawSave()
rectMode(CENTER)
resetMatrix()
fill(0, 0, 0, 160)
rect(WIDTH/2, HEIGHT/2, 512, 512)
rectMode(CORNER)
sprite("Dropbox:loadmenu", WIDTH/2, HEIGHT/2, 512, 512)
local L = WIDTH/2 - 208
local W = 400
fill(255, 0, 0, 118)
rectMode(CORNER)
if self.saveSlot == 1 then
rect(L, HEIGHT/2 + 143, 416, 65)
end
if self.saveSlot == 2 then
rect(L, HEIGHT/2 + 55, 416, 65)
end
if self.saveSlot == 3 then
rect(L, HEIGHT/2 - 32, 416, 65)
end
if self.saveSlot == 4 then
rect(L, HEIGHT/2 - 121, 416, 65)
end
if self.saveSlot == 5 then
rect(L, HEIGHT/2 - 209, 416, 65)
end
end
function Editor:draw()
world:drawEditor()
if self.showMenu then
self:drawMenu()
elseif self.showLoad then
self:drawLoad()
elseif self.showSave then
self:drawSave()
elseif self.showTiles then
self:drawTiles()
elseif self.showPickups then
self:drawPickups()
elseif self.showDecals then
self:drawDecals()
end
self.tileButton.img = self.tile.tex or "Dropbox:tiles_1"
self.pickupButton.img = self.tile.pickup or "Dropbox:pickups"
self.playButton.img = "Dropbox:play"
for _, button in ipairs(self.buttons) do
button:draw()
end
if self.tile.solid then
fill(255, 0, 0, 131)
else
fill(0, 255, 38, 83)
end
rect(self.tileButton.x, self.tileButton.y - 32, 32, 32)
end
-- handles input for environment tile selection
function Editor:touchTiles(touch)
if touch.state == ENDED then
local nb, txt = 8, "t1"
local x = math.floor((touch.x - WIDTH/2+512/2)/512*nb) + 1
local y = math.floor((touch.y - HEIGHT/2+512/2)/512*nb) + 1
local tile = "Dropbox:".. txt .. "_" .. x .. "_" .. y
if readImage(tile) then
self.tile.tex = tile
self.showTiles = false
elseif touch.x > 64 and touch.x < 192 and touch.y > HEIGHT/2 - 64 and touch.y < HEIGHT/2 + 64 then
self.tile.solid = not self.tile.solid
else
self.showTiles = false
end
end
end
-- handles input for pickup selection
function Editor:touchPickups(touch)
if touch.state == ENDED then
local nb, txt = 8, "p1"
local x = math.floor((touch.x - WIDTH/2+512/2)/512*nb) + 1
local y = math.floor((touch.y - HEIGHT/2+512/2)/512*nb) + 1
local tile = "Dropbox:".. txt .. "_" .. x .. "_" .. y
if readImage(tile) then
self.tile.pickup = tile
elseif touch.x > 64 and touch.x < 192 and touch.y > HEIGHT/2 - 64 and touch.y < HEIGHT/2 + 64 then
self.tile.pickup = "Dropbox:erase"
end
self.showPickups = false
end
end
-- handles input for decals selection
function Editor:touchDecals(touch)
if touch.state == ENDED then
local nb, txt = 8, "d1"
local x = math.floor((touch.x - WIDTH/2+512/2)/512*nb) + 1
local y = math.floor((touch.y - HEIGHT/2+512/2)/512*nb) + 1
local tile = "Dropbox:".. txt .. "_" .. x .. "_" .. y
if readImage(tile) then
self.tile.decal = tile
elseif touch.x > 64 and touch.x < 192 and touch.y > HEIGHT/2 - 64 and touch.y < HEIGHT/2 + 64 then
self.tile.decal = "Dropbox:erase"
end
self.showDecals = false
end
end
-- handles input for load menu
function Editor:touchLoad(touch)
if touch.x > WIDTH/2 - 208 and touch.x < WIDTH/2 + 208 then
if touch.x > HEIGHT/2 - 209 and touch.x < HEIGHT/2 + 143+65 then
if touch.y > HEIGHT/2 + 143 and touch.y < HEIGHT/2 + 143+65 then
self.saveSlot = 1
world:load()
elseif touch.y > HEIGHT/2 + 55 and touch.y < HEIGHT/2 + 55+65 then
self.saveSlot = 2
world:load()
elseif touch.y > HEIGHT/2 - 32 and touch.y < HEIGHT/2 + 32 then
self.saveSlot = 3
world:load()
elseif touch.y > HEIGHT/2 - 121 and touch.y < HEIGHT/2 + (-121+65) then
self.saveSlot = 4
world:load()
elseif touch.y > HEIGHT/2 - 209 and touch.y < HEIGHT/2 + (-209+65) then
self.saveSlot = 5
world:load()
end
end
end
self.showLoad = false
end
-- handles input for save menu
function Editor:touchSave(touch)
if touch.x > WIDTH/2 - 208 and touch.x < WIDTH/2 + 208 then
if touch.x > HEIGHT/2 - 209 and touch.x < HEIGHT/2 + 143+65 then
if touch.y > HEIGHT/2 + 143 and touch.y < HEIGHT/2 + 143+65 then
self.saveSlot = 1
world:save()
elseif touch.y > HEIGHT/2 + 55 and touch.y < HEIGHT/2 + 55+65 then
self.saveSlot = 2
world:save()
elseif touch.y > HEIGHT/2 - 32 and touch.y < HEIGHT/2 + 32 then
self.saveSlot = 3
world:save()
elseif touch.y > HEIGHT/2 - 121 and touch.y < HEIGHT/2 + (-121+65) then
self.saveSlot = 4
world:save()
elseif touch.y > HEIGHT/2 - 209 and touch.y < HEIGHT/2 + (-209+65) then
self.saveSlot = 5
world:save()
end
end
end
self.showSave = false
end
function Editor:scroll(touch)
-- regular scrolling if using single finger
if nbTouches == 1 then
if touch.state == BEGAN then
origin.x = touch.x
origin.y = touch.y
if touch.tapCount == 2 then
world:resize()
end
end
if touch.state == MOVING then
world.x = world.x + (origin.x - touch.x)/world.size
world.y = world.y + (origin.y - touch.y)/world.size
origin.x = touch.x
origin.y = touch.y
world.x = math.max(1, math.min(world.x, world.maxW))
world.y = math.max(1, math.min(world.y, world.maxH))
end
-- zoom in/out if using two finger gesture
end
if nbTouches == 2 then
local newPt = {}
local lastPt = {}
local ins = table.insert
for _, p in pairs(touches) do
local px = p.x
local py = p.y
ins(newPt, vec2(px, py))
if touch.state == BEGAN then
ins(lastPt, vec2(px, py))
else
ins(lastPt, vec2(px - p.deltaX, py - p.deltaY))
end
end
local delta = lastPt[1]:dist(lastPt[2]) - newPt[1]:dist(newPt[2])
world.currentScale = world.currentScale + delta/200
world.currentScale = math.max(math.min(9, world.currentScale), 3)
world:resize()
end
end
function Editor:paint(touch)
-- converts touch to map coordinates
local ix, fx = math.modf(world.x)
local iy, fy = math.modf(world.y)
local x = math.floor(touch.x/world.size + fx)
local y = math.floor(touch.y/world.size + fy)
if x <= world.maxW and y <= world.maxH then
local tile = world.map[x+ix][y+iy]
local change = false
-- tile painting
if not self.tile.pickup and self.activeButton ~= "portal" then
if tile.tex ~= self.tile.tex or tile.solid ~= self.tile.solid then
change = true
end
tile.tex = self.tile.tex or tile.tex
tile.solid = self.tile.solid
end
if touch.state == ENDED then
-- add portals
if self.activeButton == "portal" then
if tile.portal then
self:removePortal(tile)
end
tile.pickup = nil
tile.solid = false
local portal, done = self:addPortal(x+ix, y+iy)
tile.portal = portal
if done then
self:savePortal(portal, tile)
end
self.activeButton = "move"
self.scrolling = true
change = true
end
-- add pickups
if self.tile.pickup then
if tile.portal then
self:removePortal(tile)
end
if self.tile.pickup == "Dropbox:erase" then
tile.pickup = nil
else
tile.solid = false
tile.pickup = self.tile.pickup
end
if tile.pickup == "Dropbox:p1_1_7" then
world.p1flag = vec2(x+ix,y+iy)
end
if tile.pickup == "Dropbox:p1_2_7" then
world.p2flag = vec2(x+ix,y+iy)
end
if tile.pickup == "Dropbox:p1_3_7" then
world.p1spawn = vec2(x+ix,y+iy)
end
if tile.pickup == "Dropbox:p1_4_7" then
world.p2spawn = vec2(x+ix,y+iy)
end
change = true
end
-- add decals
if self.tile.decal then
if self.tile.decal == "Dropbox:erase" then
tile.decals = nil
else
tile.solid = false
if not tile.decals then
tile.decals = {}
end
table.insert(tile.decals, self.tile.decal)
end
change = true
end
end
self.saved = false
end
end
function Editor:touched(touch)
if self.showMenu then
self:touchMenu(touch)
elseif self.showLoad then
self:touchLoad(touch)
elseif self.showSave then
self:touchSave(touch)
elseif self.showTiles then
self:touchTiles(touch)
elseif self.showPickups then
self:touchPickups(touch)
elseif self.showDecals then
self:touchDecals(touch)
else
if touch.y < HEIGHT-110 then
if self.scrolling then
self:scroll(touch)
else
self:paint(touch)
end
else
if touch.state == ENDED then
for _, button in ipairs(self.buttons) do
button:touched(touch)
end
end
end
end
end
--# Button
Button = class()
function Button:init(img, x, y, type)
self.img = img
self.x = x
self.y = y
self.type = type
self.size = 64
end
function Button:draw()
local bg = ""
if editor.activeButton == self.type then
bg = "Dropbox:buttonbg_on"
else
bg = "Dropbox:buttonbg_off"
end
if self.type ~= "play" and self.type ~= "save" and self.type ~= "load" then
sprite(bg, self.x, self.y, 71, 71)
sprite(self.img, self.x, self.y, self.size - 10, self.size - 10)
sprite("Dropbox:overlay", self.x, self.y, 71, 71)
else
sprite(self.img, self.x, self.y, self.size, self.size)
if self.type == "save" and not editor.saved then
fontSize(48)
fill(255, 0, 0, 255)
text("!!!", self.x, self.y)
end
end
end
function Button:touched(touch)
local h = self.size/2
if touch.x > self.x - h and touch.x < self.x + h and touch.y > self.y - h and touch.y < self.y + h then
if self.type ~= "grid" then
editor.activeButton = self.type
editor.scrolling = false
end
if self.type == "tile" then
editor.showTiles = true
editor.tile.pickup = nil
editor.tile.decal = nil
end
if self.type == "decal" then
editor.showDecals = true
editor.showPickups = nil
editor.tile.tex = nil
end
if self.type == "pickup" then
editor.showPickups = true
editor.tile.tex = nil
editor.tile.decal = nil
end
if self.type == "portal" then
editor.tile.tex = nil
editor.tile.pickup = nil
editor.tile.decal = nil
end
if self.type == "play" then
editor.editMode = not editor.editMode
world:resize(3)
if editor.editMode then
music.stop()
editor.activeButton = "move"
editor.scrolling = true
else
music("A Hero's Quest:Battle", true, .3)
if not scene then
scene = Scene()
else
scene:reset()
end
end
end
if self.type == "grid" then
editor.showGrid = not editor.showGrid
if self.img == "Dropbox:showgrid_on" then
self.img = "Dropbox:showgrid_off"
else
self.img = "Dropbox:showgrid_on"
end
end
if self.type == "move" then
editor.scrolling = true
end
if self.type == "load" then
editor.showLoad = true
editor.activeButton = "move"
editor.scrolling = true
end
if self.type == "save" then
editor.showSave = true
editor.activeButton = "move"
editor.scrolling = true
end
end
end
--# Scene
Scene = class()
function Scene:init()
self.multi = true
self.players = {}
self.bullets = {}
self.animations = {}
self.bulletIndex = nil
self.animIndex = nil
self.p1 = Player(self, 5*80, 22*80, 1)
self.p2 = Player(self, 10*80, 7*80, -1)
self.p1.target = self.p2
self.p2.target = self.p1
table.insert(self.players, self.p1)
table.insert(self.players, self.p2)
self.p1img = image(WIDTH/2, HEIGHT)
self.p2img = image(WIDTH/2, HEIGHT)
end
function Scene:reset()
self:init()
end
function Scene:update()
-- update timers (respawns, etc...)
world:update()
self.p1:update()
self.p2:update()
-- process bullet collisions etc...
self.bulletIndex = nil
for index, bullet in ipairs(self.bullets) do
if bullet.gone then
self.bulletIndex = index
else
bullet:update()
end
end
self.animIndex = nil
for index, animation in ipairs(self.animations) do
if animation.done then
self.animIndex = index
else
animation:update()
end
end
end
function Scene:showStats(player)
fill(0, 0, 0, 150)
local offset = 0
if player.side == -1 then
offset = WIDTH/2
end
rect(offset, 0, WIDTH/2, HEIGHT)
pushMatrix()
fill(255, 0, 0, 255)
translate(offset + WIDTH/4, HEIGHT/2)
rotate(-90*player.side)
fontSize(30)
if not player.spawned then
text("You died !", 0, 200)
end
text("Flag caps: " .. player.score.caps, 0, 100)
text("Kills: " .. player.score.kills, 0, 0)
text("Deaths: " .. player.score.deaths, 0, -100)
if not player.spawned then
text("Respawning in " .. math.floor(player.spawnTimer*10)/10 .. "s", 0, -200)
end
popMatrix()
end
function Scene:draw()
-- if multi we use clip() to splitscreen
if self.multi then
clip(0, 0, WIDTH/2, HEIGHT)
end
world:draw(self.p1)
if self.p1.spawned then
self.p1:draw()
else
self:showStats(self.p1)
end
tint(255)
if self.multi then
clip(WIDTH/2, 0, WIDTH/2, HEIGHT)
world:draw(self.p2)
if self.p2.spawned then
self.p2:draw()
else
self:showStats(self.p2)
end
tint(255)
clip()
-- vignette overlay and separation
rotate(-90)
sprite("Dropbox:0001", - HEIGHT/2, WIDTH/4, HEIGHT, WIDTH/2)
rotate(180)
sprite("Dropbox:0001", HEIGHT/2, -WIDTH/2 - WIDTH/4 , HEIGHT, WIDTH/2)
rotate(-90)
strokeWidth(2)
stroke(0, 0, 0, 255)
line(WIDTH/2, 1, WIDTH/2, HEIGHT)
else
sprite("Dropbox:0001", WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
end
end
-- handle player controls
function Scene:touched(touch)
if touch.x < WIDTH/2 then
if self.p1.spawned then
if touch.y < HEIGHT/2 then
self.p1.shootStick:touched(touch)
else
self.p1.moveStick:touched(touch)
end
end
else
if self.p2.spawned then
if touch.y < HEIGHT/2 then
self.p2.moveStick:touched(touch)
else
self.p2.shootStick:touched(touch)
end
end
end
end
--# World
World = class()
function World:init(w, h)
self.w = w
self.h = h
self.maxW = w*3
self.maxH = h*3
self.currentScale = 3
self.size = WIDTH/w
-- attempts to load last map
self:load()
end
-- resizes tiles for zoom in/out using editor
function World:resize(s)
if s then
self.w = 4*s
self.h = 3*s
self.size = WIDTH/self.w
else
self.w = 4*self.currentScale
self.h = 3*self.currentScale
end
self.size = WIDTH/self.w
end
function World:load()
local slot = "Dropbox:save_" .. editor.saveSlot
local backup = readText(slot)
self.timers = {}
self.p1flag = vec2()
self.p1spawn = vec2()
self.p2flag = vec2()
self.p2spawn = vec2()
self.x = 1
self.y = 1
if backup then
self.map = json.decode(backup)
self.maxW = #self.map
self.maxH = #self.map[1]
else
self.map = {}
for x=1, self.maxW do
self.map[x] = {}
for y=1, self.maxH do
self.map[x][y] = Tile()
end
end
end
self.maxW = #self.map
self.maxH = #self.map[1]
-- reads map info (flags, spawns, etc)
local block
for x=1, self.maxW do
for y=1, self.maxH do
block = self.map[x][y]
-- if block.tex and not readImage(block.tex) then
-- block.tex = nil
-- end
-- if block.decal then
-- block.decals = {block.decal}
-- end
if block.pickup == "Dropbox:p1_1_7" then self.p1flag = vec2(x,y) end
if block.pickup == "Dropbox:p1_2_7" then self.p2flag = vec2(x,y) end
if block.pickup == "Dropbox:p1_3_7" then self.p1spawn = vec2(x,y) end
if block.pickup == "Dropbox:p1_4_7" then self.p2spawn = vec2(x,y) end
end
end
end
function World:save()
local slot = "Dropbox:save_" .. editor.saveSlot
saveText(slot, json.encode(self.map))
editor.saved = true
end
-- process timers
function World:processTimer(timer)
-- respawn timer done, repop pickup
if timer.type == "pickup" then
self.map[timer.obj.pos.x][timer.obj.pos.y].pickup = timer.obj.item
end
end
function World:addRespawnTimer(i, p)
table.insert(self.timers, Timer(15, "pickup", {item = i, pos = vec2(p.x, p.y)} ))
end
-- update timers
function World:update()
for _, timer in ipairs(self.timers) do
timer:update()
end
local timer = self.timers[1]
if timer and timer.done then
self:processTimer(timer)
table.remove(self.timers, 1)
end
end
function World:drawEditor()
local ix, fx = math.modf(self.x)
local fx = fx * self.size
local iy, fy = math.modf(self.y)
local fy = fy * self.size
local px, py = 0, 0
pushMatrix()
translate(self.size/2 - fx, self.size/2 - fy)
for x=ix, ix + self.w do
for y=iy, iy + self.h do
if x <= self.maxW and y <= self.maxH and x>=1 and y>=1 then
block = self.map[x][y]
if block.tex then
sprite(block.tex, px, py, self.size)
end
if block.decals then
for i=1, #block.decals do
sprite(block.decals[i], px, py, self.size)
end
end
if block.portal then
if block.portal.dx then
tint(block.portal.r, block.portal.g, block.portal.b, 140)
end
sprite("Dropbox:portal", px, py, self.size)
tint(255)
end
if block.pickup then
sprite("Dropbox:light", px, py, self.size)
sprite(block.pickup, px, py, self.size)
end
if editor.showGrid then
if block.solid then
fill(255, 0, 0, 30)
else
fill(0, 255, 0, 30)
end
rect(px - self.size/2, py - self.size/2, self.size, self.size)
end
end
py = py + self.size
end
px = px + self.size
py = 0
end
popMatrix()
end
function World:draw(player)
local block = nil
strokeWidth(1)
local bigfastmorph = self.size - math.abs(math.sin(ElapsedTime*5)*20)
local bigslowmorph = self.size - math.abs(math.sin(ElapsedTime*2)*20)
local smallfastmorph = self.size - math.abs(math.sin(ElapsedTime*5)*10)
local smallslowmorph = self.size - math.abs(math.sin(ElapsedTime*2)*10)
local mx, my = player.pos.x/world.size + 1, player.pos.y/world.size + 1
local ix, fx = math.modf(mx)
local fx = fx * self.size
local iy, fy = math.modf(my)
local fy = fy * self.size
local px, py = 0, 0
pushMatrix()
local w = 3
if scene.multi then
translate(WIDTH/4 + world.size/2 - fx, HEIGHT/2 + world.size/2 - fy)
else
w = 6
translate(WIDTH/2 + world.size/2 - fx, HEIGHT/2 + world.size/2 - fy)
end
if player.side == 1 then
px = -w*self.size
else
px = -3*self.size + WIDTH/2
end
py = -5*self.size
for x=ix-w, ix + w do
for y=iy-5, iy + 5 do
if x <= self.maxW and y <= self.maxH and x>0 and y>0 then
block = self.map[x][y]
-- read and display tile content
if block.tex then
sprite(block.tex, px, py, self.size)
end
if block.decals then
for i=1, #block.decals do
sprite(block.decals[i], px, py, self.size)
end
end
if block.portal then
tint(block.portal.r, block.portal.g, block.portal.b, 140)
pushMatrix()
translate(px, py)
rotate(ElapsedTime*300)
sprite("Dropbox:portal", 0, 0, bigslowmorph)
popMatrix()
tint(255)
end
if block.pickup then
sprite("Dropbox:light", px, py, bigfastmorph)
sprite(block.pickup, px, py, smallslowmorph)
end
end
py = py + self.size
end
px = px + self.size
py = -5*self.size
end
popMatrix()
end
--# Player
Player = class()
function Player:init(scene, x, y, side)
self.scene = scene
self.side = side
self.score = {}
self.score.caps = 0
self.score.kills = 0
self.score.deaths = 0
if side == 1 then
self.flag = "Dropbox:p1_1_7"
self.spawn = "Dropbox:p1_3_7"
else
self.flag = "Dropbox:p1_2_7"
self.spawn = "Dropbox:p1_4_7"
end
self.hasFlag = false
self.flagStolen = false
self.target = nil
self.pos = vec2(x, y)
self.mpos = vec2()
self.dir = vec2(1, 0)
self.moveStick = Controller(self, true)
self.shootStick = Controller(self)
self.status = "idle"
self.model = Model(self)
self.arrow = mesh()
self.arrowId = self.arrow:addRect(0, 0, 150, 150)
self.arrow.texture = "Dropbox:arrow"
self.arrowTimer = 0
self:respawn()
self.shield = 0
self.spawned = true
self.invincible = false
end
function Player:move(vec, val)
local npx = self.pos.x + vec.x * val
local npy = self.pos.y + vec.y * val
local nx, ny = math.floor(npx/world.size) + 1, math.floor(npy/world.size) + 1
if ny > 0 and ny <= world.maxH and not world.map[self.mpos.x][ny].solid then
self.pos.y = npy
end
if nx > 0 and nx <= world.maxW and not world.map[nx][self.mpos.y].solid then
self.pos.x = npx
end
end
-- equips a weapon
function Player:setWeapon(weapon)
self.weapon = weapon
if self.weapon ~= Weapons.railgun then
self.aiming = false
end
self.ammo = self.weapon.ammo
self:updateSpeed()
end
-- respawn player to spawn point
function Player:respawn()
if self.hasFlag then
self:dropFlag()
end
if self.side == 1 then
self.pos = vec2(world.p1spawn.x*world.size-world.size/2, world.p1spawn.y*world.size-world.size/2)
else
self.pos = vec2(world.p2spawn.x*world.size-world.size/2, world.p2spawn.y*world.size-world.size/2)
end
self.aiming = false
self.leftPortal = true
self.invincible = true
self.invinTimer = 2
self.spawned = false
self.timer = 0
self.shield = 0
self.status = "idle"
self.aiming = false
self.firing = false
self.ready = true
self.showFlash = false
self.showHit = false
self.needReload = false
self.spawnTimer = 8
self.knockedBack = false
self.knockBackDir = vec2()
self.knockBackPow = 0
self.hasSpeedboost = false
self.speedboostTimer = 0
self.isSnared = false
self.snareTimer = 0
repeat
-- self.pos = vec2(math.random(world.maxW*world.size-1), math.random(world.maxH*world.size-1))
local x, y = math.floor(self.pos.x/world.size) + 1, math.floor(self.pos.y/world.size) + 1
until true --world.map[x][y].solid ~= true
self:setWeapon(Weapons.gun)
self.life = 100
end
function Player:updateSpeed()
local weight = self.weapon.weight
if self.hasSpeedboost then
weight = weight - 3
end
if self.hasFlag then
weight = weight + 1
end
if self.isSnared then
weight = weight + 2
end
self.speed = 5.5 - weight
end
-- handles flag capture
function Player:capFlag()
if not self.flagStolen then
sound("A Hero's Quest:Level Up")
sound("Game Sounds One:Crowd Cheer")
local block
for x=1, world.maxW do
for y=1, world.maxH do
block = world.map[x][y]
if block.pickup == "Dropbox:p1_1_7" then block.pickup = nil end
if block.pickup == "Dropbox:p1_2_7" then block.pickup = nil end
end
end
if self.side == 1 then
world.map[world.p1flag.x][world.p1flag.y].pickup = self.flag
world.map[world.p2flag.x][world.p2flag.y].pickup = self.target.flag
else
world.map[world.p1flag.x][world.p1flag.y].pickup = self.target.flag
world.map[world.p2flag.x][world.p2flag.y].pickup = self.flag
end
self.target.hasFlag = false
self.hasFlag = false
self.flagStolen = false
self.target.flagStolen = false
self.score.caps = self.score.caps + 1
end
end
-- handles flag stealing
function Player:stealFlag(tile)
self.hasFlag = true
sound("A Hero's Quest:Pick Up")
tile.pickup = nil
self.target.flagStolen = true
end
-- handles flag save
function Player:returnFlag(tile)
tile.pickup = nil
sound("Game Sounds One:Crowd Cheer")
if self.side == 1 then
world.map[world.p1flag.x][world.p1flag.y].pickup = self.flag
else
world.map[world.p2flag.x][world.p2flag.y].pickup = self.flag
end
self.flagStolen = false
end
-- handles flag drop
function Player:dropFlag()
self.hasFlag = false
world.map[self.mpos.x][self.mpos.y].pickup = self.target.flag
-- sound("A Hero's Quest:Pick Up")
end
-- handles picking up item
function Player:pickup(tile)
-- flag
if self.hasFlag then
if tile.pickup == self.spawn then
self:capFlag()
end
elseif tile.pickup == self.target.flag then
self:stealFlag(tile)
end
if tile.pickup == self.flag and self.flagStolen then
self:returnFlag(tile)
end
-- speedboost
if tile.pickup == "Dropbox:p1_1_6" then
sound("A Hero's Quest:FireBall Woosh")
self.hasSpeedboost = true
self.boostTimer = 2
self:resetPickup(tile)
end
-- shield
if tile.pickup == "Dropbox:p1_2_6" then
sound("A Hero's Quest:Steal")
self.shield = self.shield + 50
if self.shield > 100 then self.shield = 100 end
self:resetPickup(tile)
end
-- life
if tile.pickup == "Dropbox:p1_1_8" then
sound("A Hero's Quest:Eat 1")
self.life = 100
self:resetPickup(tile)
end
-- flak
if tile.pickup == "Dropbox:p1_2_8" then
sound("Game Sounds One:Reload 2")
self:setWeapon(Weapons.flak)
self:resetPickup(tile)
end
-- minigun
if tile.pickup == "Dropbox:p1_3_8" then
sound("Game Sounds One:Reload 2")
self:setWeapon(Weapons.minigun)
self:resetPickup(tile)
end
-- blastgun
if tile.pickup == "Dropbox:p1_4_8" then
sound("Game Sounds One:Reload 2")
self:setWeapon(Weapons.blastgun)
self:resetPickup(tile)
end
-- railgun
if tile.pickup == "Dropbox:p1_5_8" then
sound("Game Sounds One:Reload 2")
self:setWeapon(Weapons.railgun)
self:resetPickup(tile)
end
-- rocketlauncher
if tile.pickup == "Dropbox:p1_6_8" then
sound("Game Sounds One:Reload 2")
self:setWeapon(Weapons.rocketlauncher)
self:resetPickup(tile)
end
end
-- once an item is picked up, starts its respawn timer (handled by World class)
function Player:resetPickup(tile)
world:addRespawnTimer(tile.pickup, self.mpos)
tile.pickup = nil
end
-- handles entering a portal
function Player:portalTo(tile)
if self.leftPortal then
table.insert(scene.animations, Animation("electrik", self.pos))
table.insert(scene.animations, Animation("electrik", self.pos))
self.pos.x = tile.portal.dx * world.size - world.size/2
self.pos.y = tile.portal.dy * world.size - world.size/2
self.mpos = vec2(tile.portal.dx, tile.portal.dy)
self.leftPortal = false
sound("A Hero's Quest:Attack Cast 1")
table.insert(scene.animations, Animation("electrik", self.pos, false, .1, 2))
table.insert(scene.animations, Animation("electrik", self.pos, false, .1, 2))
end
end
-- updates player timers and player map coordinates
function Player:update()
if self.knockedBack then
self.knockBackPow = self.knockBackPow - self.knockBackPow/5
self:move(self.knockBackDir, self.knockBackPow)
if self.knockBackPow < 1 then
self.knockedBack = false
self.knockBackPow = 0
end
end
if not self.spawned then
self.spawnTimer = self.spawnTimer - DeltaTime
if self.spawnTimer < 0 then
self.spawned = true
end
end
if self.isSnared then
self.snareTimer = self.snareTimer - DeltaTime
if self.snareTimer < 0 then
self.isSnared = false
self:updateSpeed()
end
end
if self.hasSpeedboost then
self.boostTimer = self.boostTimer - DeltaTime
if self.boostTimer < 0 then
self.hasSpeedboost = false
self:updateSpeed()
end
end
self.mpos = vec2(math.floor(self.pos.x/world.size) + 1, math.floor(self.pos.y/world.size) + 1)
local pos = world.map[self.mpos.x][self.mpos.y]
if pos.pickup then
self:pickup(pos)
self:updateSpeed()
end
if pos.portal then
self:portalTo(pos)
else
self.leftPortal = true
end
if self.ammo < 1 then
self:setWeapon(Weapons.gun)
end
self.model:update()
if self.life <= 0 then
sound("A Hero's Quest:Hurt 5")
self:respawn()
end
if self.spawned and self.invincible then
self.invinTimer = self.invinTimer - DeltaTime
if self.invinTimer < 0 then
self.invinTimer = 3
self.invincible = false
end
end
self.timer = self.timer + DeltaTime
self.arrowTimer = self.arrowTimer + DeltaTime/2
if self.arrowTimer > 1 then
self.arrowTimer = 0
end
if self.weapon == Weapons.flak then
if self.needReload and self.timer > self.weapon.rate/3 then
sound("Game Sounds One:Reload 1")
self.needReload = false
end
end
if self.timer > self.weapon.rate then
self.timer = 0
self.ready = true
if self.firing then
self:fire()
end
end
end
function Player:draw()
-- positions the player and opponent if multi
local offset = 0
if self.side == -1 then
offset = WIDTH/2
end
local p1offset
local v = self.target.pos - self.pos
if scene.multi then
p1offset = vec2(offset + WIDTH/4, HEIGHT/2)
else
p1offset = vec2(offset + WIDTH/2, HEIGHT/2)
end
local p2offset = v + p1offset
local pos = self.pos - p1offset
-- draws all bullets
for _, bullet in ipairs(scene.bullets) do
bullet:draw(pos)
end
-- draws the red beam for railgun
if self.aiming then
local offset = vec2(0, -9):rotate(math.atan(self.dir.y, self.dir.x))
stroke(255, 0, 0, 49)
strokeWidth(10)
local A = p1offset + offset + self.dir * 45
local B = p1offset + offset + self.dir * self.weapon.range
line(A.x, A.y, B.x, B.y)
end
if self.target.aiming then
stroke(255, 0, 0, 49)
strokeWidth(10)
local A = p2offset + self.target.dir * 45
local B = p2offset + self.target.dir * self.target.weapon.range
line(A.x, A.y, B.x, B.y)
end
strokeWidth(2)
stroke(0, 0, 0, 255)
pushMatrix()
translate(p1offset.x, p1offset.y)
-- shows opponent position indicator
if self.target.spawned then
local ang = math.atan2(v.y, v.x)
self.arrow:setRect(self.arrowId, 0, 0, self.arrowTimer*250, self.arrowTimer*250, ang)
self.arrow:setRectColor(self.arrowId, 255, 255, 255, 255-self.arrowTimer*255)
self.arrow:draw()
end
-- display ammo and player
fill(255)
rotate(-90*self.side)
text(self.ammo, 0, 50)
rotate(90*self.side)
self:render()
popMatrix()
if self.target.spawned then
pushMatrix()
translate(p2offset.x, p2offset.y)
self.target:render()
popMatrix()
end
--[[
if self.showHit and self.shield > 0 then
pushMatrix()
translate(p1offset.x, p1offset.y)
tint(255, 255, 255, 131)
sprite("Dropbox:shield", 0, 0, world.size)
popMatrix()
end
]]--
for _, animation in ipairs(scene.animations) do
animation:draw(pos)
end
self.showFlash = false
self.showHit = false
end
function Player:render()
-- draws light flash or player shadow
if self.showFlash then
if self.weapon == Weapons.blastgun then
sprite("Dropbox:blast2", 0, 0, 250, 250)
else
sprite("Dropbox:bullet", 0, 0, 250, 250)
end
else
sprite("Dropbox:Shadow", 0, 0, 80, 96)
end
-- draws the player
if self.invincible then
rotate(math.atan(self.dir.y, self.dir.x)*180/math.pi)
if self.showFlash then
if self.weapon == Weapons.blastgun then
tint(255, 0, 201, 250)
else
tint(255, 255, 255, 250)
end
sprite("Dropbox:muzzle", 73, -9)
end
if math.floor(self.invinTimer*5)%2==0 then
self.model:draw()
end
else
-- display lifebar
local life = self.life*2.55
fill(255 - life, life, 0, 255)
rect(-self.side * 32, -self.life/4, 10, self.life/2)
-- display shield
local shield = self.shield*2.55
fill(0, 127, 255, 255)
rect(-self.side * 48, -self.shield/4, 10, self.shield/2)
rotate(math.atan(self.dir.y, self.dir.x)*180/math.pi)
if self.showFlash then
if self.weapon == Weapons.blastgun then
tint(255, 0, 201, 250)
else
tint(255, 255, 255, 250)
end
sprite("Dropbox:muzzle", 73, -9)
end
self.model:draw()
end
end
function Player:drawGUI()
if self.spawned then
self.moveStick:draw()
self.shootStick:draw()
end
end
-- sets status ("moving", "idle", etc...)
function Player:setStatus(status)
if self.status ~= status then
self.status = status
self.model.currentFrame = 1
end
end
-- handles pressing the fire button
function Player:fire(first)
-- first shot
if first then
self.firing = true
if self.ready then
self.showFlash = true
self.timer = 0
end
end
-- weapon ready to fire
if self.ready then
self.ammo = self.ammo - 1
self.showFlash = true
-- adds a bullet
local offset = vec2(0, -9):rotate(math.atan(self.dir.y, self.dir.x))
local spawn = self.pos + offset + self.dir * 45
if self.weapon == Weapons.blastgun or self.weapon == Weapons.flak then
local x, y = math.floor(spawn.x/world.size) + 1, math.floor(spawn.y/world.size) + 1
if world.map[x][y].solid then
spawn = self.pos + offset - self.dir * 5
end
end
table.insert(self.scene.bullets, Bullet(self, spawn.x, spawn.y, self.weapon))
-- or 10 for the flak
if self.weapon == Weapons.flak then
for i=1, 9 do
table.insert(self.scene.bullets, Bullet(self, spawn.x, spawn.y, self.weapon))
end
self.needReload = true
for i=1, 4 do
sound("Game Sounds One:Pistol")
end
elseif self.weapon == Weapons.railgun then
for i=1, 10 do
sound("Game Sounds One:Blaster")
end
elseif self.weapon == Weapons.blastgun then
sound("Game Sounds One:Blaster")
elseif self.weapon == Weapons.rocketlauncher then
sound("A Hero's Quest:FireBall Blast 1", .5)
else
sound("Game Sounds One:Pistol")
end
end
self.ready = false
end
--# Bullet
Bullet = class()
function Bullet:init(owner, x, y, weapon)
self.owner = owner
self.origin = vec2(x, y)
self.pos = vec2(x, y)
-- handles weapon spray
if weapon == Weapons.flak then
self.dir = vec2(self.owner.dir.x + (-1+math.random()*2)/5, self.owner.dir.y + (-1+math.random()*2)/5)
elseif weapon == Weapons.blastgun then
self.dir = vec2(self.owner.dir.x + (-1+math.random()*2)/10, self.owner.dir.y + (-1+math.random()*2)/10)
else
self.dir = vec2(self.owner.dir.x, self.owner.dir.y)
end
self.weapon = weapon
self.speed = weapon.speed
self.dmg = weapon.dmg
self.range = weapon.range
self.distance = 0
self.gone = false
self.impact = false
self.timer = 0
if weapon == Weapons.rocketlauncher then
table.insert(scene.animations, Animation("smoke", self.pos))
end
end
function Bullet:hit(vol)
if self.weapon == Weapons.flak then
sound("Game Sounds One:Pillar Hit", vol/10)
elseif self.weapon == Weapons.rocketlauncher then
sound("A Hero's Quest:FireBall Blast 2", vol)
else
sound("Game Sounds One:Pillar Hit", vol)
end
end
function Bullet:update()
self.timer = self.timer + DeltaTime
local vol = math.min(50/self.pos:dist(self.owner.pos), .8) --math.min(50/self.distance, .8)
local bounce = self.weapon == Weapons.blastgun or self.weapon == Weapons.flak
self.lastpos = self.pos
local x, y = math.floor(self.pos.x/world.size) + 1, math.floor(self.pos.y/world.size) + 1
local npx = self.pos.x + self.dir.x * self.speed * 60*DeltaTime
local npy = self.pos.y + self.dir.y * self.speed * 60*DeltaTime
local nx, ny = math.floor(npx/world.size) + 1, math.floor(npy/world.size) + 1
local hit = false
-- handles weather a bullet hits or bounces off ball.
if world.map[nx][y].solid then
if bounce then
self.dir.x = -self.dir.x
else
hit = true
self.gone = true
end
self.impact = true
self:hit(vol)
end
if world.map[x][ny].solid then
if bounce then
self.dir.y = -self.dir.y
else
hit = true
self.gone = true
end
self.impact = true
self:hit(vol)
end
self.pos = self.pos + self.dir * self.speed * 60*DeltaTime
if self.weapon == Weapons.rocketlauncher then
if self.timer > .1 then
self.timer = 0
table.insert(scene.animations, Animation("smoke", self.pos, false, .1, 1, .7))
end
end
if self.impact and self.weapon == Weapons.rocketlauncher then
local dist = self.pos:dist(self.owner.pos)
if dist < 150 then
self:dealDmg(self.owner, (self.dmg - dist)/2)
end
local dist = self.pos:dist(self.owner.target.pos)
if dist < 150 then
self:dealDmg(self.owner.target, (self.dmg - dist)/2)
end
table.insert(scene.animations, Animation("smoke", self.pos, false, .1, 3.3, .7))
table.insert(scene.animations, Animation("explosion", self.pos, false, .1, 2, .7))
table.insert(scene.animations, Animation("explosion", self.pos, false, .1, 2, .7))
return
end
-- update distance travelled by bullet
self.distance = self.distance + self.pos:dist(self.lastpos)
if self.distance > self.range then
self.gone = true
return
end
-- if it can bleed, we can kill it
if not self.owner.target.invincible and self.pos:dist(self.owner.target.pos) < 40 then
if self.weapon == Weapons.rocketlauncher then
local dist = self.pos:dist(self.owner.pos)
if dist < 150 then
self:dealDmg(self.owner, (self.dmg - dist)/4)
end
table.insert(scene.animations, Animation("smoke", self.pos, false, .1, 3.3, .7))
table.insert(scene.animations, Animation("explosion", self.pos, false, .1, 2, .7))
table.insert(scene.animations, Animation("explosion", self.pos, false, .1, 2, .7))
end
self:dealDmg(self.owner.target, self.dmg, true)
self.impact = true
self.gone = true
self:hit(vol)
return
end
end
function Bullet:dealDmg(target, dmg, face)
-- calculate possible shield absorb
local realDmg = 0
if target.shield == 0 then
realDmg = dmg
else
target.shield = target.shield - dmg
if target.shield > 0 then
realDmg = dmg/2
else
realDmg = dmg + target.shield/2
target.shield = 0
end
end
target.life = target.life - realDmg
target.showHit = true
target.isSnared = true
target.snareTimer = .5
target:updateSpeed()
if target.life > 0 then
sound("A Hero's Quest:Hurt 1")
end
target.knockedBack = true
target.knockBackPow = target.knockBackPow + dmg/5
if face then
target.knockBackDir = target.side * (self.pos - target.pos):normalize()
else
target.knockBackDir = -target.side * (self.pos - target.pos):normalize()
end
table.insert(scene.animations, Animation("blood", target.pos, false, .1, 2, .7))
if target.life <= 0 then
target.score.deaths = target.score.deaths + 1
if target ~= self.owner then
self.owner.score.kills = self.owner.score.kills + 1
else
self.owner.score.kills = self.owner.score.kills - 1
end
table.insert(scene.animations, Animation("blood", target.pos, false, .1, 3, .7))
table.insert(scene.animations, Animation("blood", target.pos, false, .1, 3, .7))
table.insert(scene.animations, Animation("blood", target.pos, false, .1, 3, .7))
end
end
function Bullet:draw(vec)
local pos = self.pos - vec
pushMatrix()
translate(pos.x, pos.y)
if self.impact then
if self.weapon == Weapons.blastgun then
tint(255, 255, 255, 255)
sprite("Dropbox:blast2", 0, 0, 128, 128)
else
tint(255, 240, 0, 255)
sprite("Dropbox:bullet", 0, 0, 128, 128)
end
else
tint(255, 255, 255, 255)
if self.weapon == Weapons.blastgun then
sprite("Dropbox:blast", 0, 0, 64, 64)
elseif self.weapon == Weapons.rocketlauncher then
sprite("Dropbox:bullet", 0, 0, 96, 96)
else
sprite("Dropbox:bullet", 0, 0, 64, 64)
end
end
popMatrix()
end
--# Controller
Controller = class()
function Controller:init(owner, moveStick)
self.isMoveStick = moveStick
self.owner = owner
self.active = false
self.moving = false
self.pos = vec2()
self.origin = vec2()
self.dist = 0
self.lastDir = vec2(1, 0)
end
function Controller:draw()
if self.active then
local vec = (self.pos - self.origin)
local dir = vec2(0,0)
if vec ~= dir then
dir = vec:normalize()
else
dir = self.lastDir
end
if self.isMoveStick then
local val = self.owner.speed * self.dist/50 * 60*DeltaTime
self.owner:move(dir, val)
if self.owner.shootStick.moving == false then
self.owner.dir = dir
end
else
if self.moving then
self.owner.dir = dir
end
end
self.lastDir = dir
sprite("Dropbox:stick", self.pos.x, self.pos.y)
sprite("Dropbox:stick_bg", self.origin.x, self.origin.y)
end
end
function Controller:touched(touch)
self.active = true
if touch.state == BEGAN then
if not self.isMoveStick then
if self.owner.weapon == Weapons.railgun then
self.owner.aiming = true
else
self.owner:fire(true)
end
end
self.origin.x = touch.x
self.origin.y = touch.y
end
self.pos.x = touch.x
self.pos.y = touch.y
self.dist = self.pos:dist(self.origin)
if self.dist > 10 then
self.moving = true
end
if self.isMoveStick then
if self.dist > 15 then
self.dist = self.dist - 15
self.owner:setStatus("moving")
else
self.dist = 0
self.owner:setStatus("idle")
end
end
if touch.state == ENDED then
self.active = false
self.moving = false
if self.isMoveStick then
self.owner:setStatus("idle")
else
if self.owner.weapon == Weapons.railgun then
self.owner.aiming = false
self.owner:fire(true)
end
self.owner.firing = false
end
end
if self.dist > 50 then self.dist = 50 end
end
--# Weapons
Weapons = {}
Weapons.gun = {
dmg = 20,
rate = .4,
speed = 10,
range = 500,
ammo = 1/0,
weight = 1.0
}
Weapons.minigun = {
dmg = 8,
rate = .1,
speed = 15,
range = 800,
ammo = 60,
weight = 2.0
}
Weapons.blastgun = {
dmg = 15,
rate = .2,
speed = 10,
range = 1000,
ammo = 60,
weight = 2.5
}
Weapons.flak = {
dmg = 10,
rate = 1.5,
speed = 10,
range = 300,
ammo = 15,
weight = 1.5
}
Weapons.railgun = {
dmg = 150,
rate = 2,
speed = 40,
range = 1000,
ammo = 5,
weight = 3
}
Weapons.rocketlauncher = {
dmg = 100,
rate = 1,
speed = 10,
range = 3000,
ammo = 10,
weight = 3
}
--# Model
Model = class()
function Model:init(owner)
self.owner = owner
self.frames = {
idle = {
{1/3, 1/3}
},
moving = {
{1/3, 1/3},
{2/3, 1/3},
{1/3, 1/3},
{0, 1/3}
}}
self.currentFrame = 1
self.timer = 0
self.mdl = mesh()
self.id = self.mdl:addRect(0, 0, 160/3, 105,-math.pi/2)
if self.owner.side == 1 then
self.mdl.texture = "Dropbox:Toon"
else
self.mdl.texture = "Dropbox:Toon2"
end
local frame = self.frames[self.owner.status][self.currentFrame]
self.mdl:setRectTex(self.id, frame[1], 0, frame[2], 1)
end
function Model:update()
self.timer = self.timer + DeltaTime
if self.timer > .1 then
self.timer = 0
self.currentFrame = self.currentFrame + 1
if self.currentFrame > #self.frames[self.owner.status] then
self.currentFrame = 1
end
local frame = self.frames[self.owner.status][self.currentFrame]
self.mdl:setRectTex(self.id, frame[1], 0, frame[2], 1)
end
end
function Model:draw()
if self.owner.showFlash then
self.mdl:setRectColor(self.id, 1000, 1000, 1000, 255)
elseif self.owner.showHit then
self.mdl:setRectColor(self.id, 10000, 0, 0, 255)
else
self.mdl:setRectColor(self.id, 255, 255, 255, 255)
end
self.mdl:draw()
end
--# Timer
Timer = class()
function Timer:init(duration, type, obj)
self.type = type
self.obj = obj
self.done = false
self.start = os.clock()
self.max = self.start + duration
end
function Timer:update()
self.start = self.start + DeltaTime
if self.start > self.max then
self.done = true
end
end
--# Tile
Tile = class()
function Tile:init()
self.tex = nil
self.solid = 0
self.pickup = nil
self.decals = {}
self.portal = nil
-- only used for editor
self.decal = nil
end