--# 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