local texture = nil
local guard_texture = nil
local effect_texture = nil
local slashes_texture = nil
local swords_texture = nil
local alert = nli
local shadow_texture = nil
local anim = nil

local THROW_SFX = nil
local FIRE_PROJECTILE_SFX = nil
local FIRE_KICK = nil
local FLAME_PILLAR_SFX = nil
local SEARCH_SFX = nil
local LOCK_ON_SFX = nil
local APPEAR_SFX = nil
local BREAK_SFX = nil
local SPIN_SFX = nil
local SNAKE_SFX = nil
local APOLLO_SFX = nil
local EXPLOSION_SFX = nil

local HIT_SFX = nil
local SWORD_SFX = nil
local DARK_SFX = nil

local RAIN_SFX = nil
local LASER_SFX = nil
local SPAWN_SFX = nil
local GUARD_SFX = nil
local SHOOT_SFX = nil


function package_init(self)
    texture = Engine.load_texture(_modpath.."yumeko.png")
    guard_texture = Engine.load_texture(_modpath.."guard.png")
    effect_texture = Engine.load_texture(_modpath.."effects.png")
    slashes_texture = Engine.load_texture(_modpath.."slashes.png")
    swords_texture = Engine.load_texture(_modpath.."swords.png")
    shotgun_texture = Engine.load_texture(_modpath.."shotgun_effect.png")
    shell_texture = Engine.load_texture(_modpath.."shell.png")

  --  alert = Engine.load_texture(_modpath.."overlay_fx07_animations.png")
    --hit_effects_texture = Engine.load_texture(_modpath.."hit_effects.png")
    self:set_name("Yumeko")
	local rank = self:get_rank()
    self.damage = 80
    self.shotgun_damage = 20

	if rank == Rank.V1 then
    	self:set_health(2400)
	else
		self:set_health(5000)

	end
	self:set_element(Element.None)
    self:set_texture(texture, true)
    self:set_height(60)
    self:share_tile(false)
    self.max_health = self:get_health()
    self:set_explosion_behavior(8, 8, true)


    anim = self:get_animation()
    anim:load(_modpath.."yumeko.animation")

    HIT_SFX = Engine.load_audio(_folderpath.."hit.ogg")
    SWORD_SFX = Engine.load_audio(_folderpath.."swing_sword.ogg")
    DARK_SFX = Engine.load_audio(_folderpath.."dark.ogg")
    EXPLOSION_SFX = Engine.load_audio(_folderpath.."explosion.ogg")
    APPEAR_SFX = Engine.load_audio(_folderpath.."appear.ogg")
    THROW_SFX = Engine.load_audio(_folderpath.."toss_item.ogg")


    DASH_SFX = Engine.load_audio(_folderpath.."dash.ogg")

    GUARD_SFX = Engine.load_audio(_folderpath.."tink.ogg")
    SHOOT_SFX = Engine.load_audio(_folderpath.."cannon.ogg")
    KNIFE_SFX = Engine.load_audio(_folderpath.."knife.ogg")
    LANCE_SFX = Engine.load_audio(_folderpath.."lance.ogg")


    init_boss(self)

end

function init_boss(self)
    -- Setting names here is just convenience if I want to print the state I'm in later
    self.states = {
        idle = {name = "idle", func = idle},
        move = {name = "move", func = move},
        flinch = {name = "flinch", func = flinch},
        
        start_sub_pattern = {name = "start_sub_pattern"},
        finish_sub_pattern = {name = "finish_sub_pattern"},
        skip_if_above_x = {name = "skip_if_above_x", val = 50},
        finish_skip = {name = "finish_skip"},
        start_optional = {name = "start_optional"},
        finish_optional = {name = "finish_optional"},

        move_line_up = {name = "move_line_up", func = move_line_up},
        move_to_back = {name = "move_to_back", func = move_to_back},
        move_to_back_center = {name = "move_to_back_center", func = move_to_back_center},
        move_near_front = {name = "move_near_front", func = move_near_front},
        move_to_front = {name = "move_to_front", func = move_to_front},
        move_to_front_center = {name = "move_to_front_center", func = move_to_front_center},


        move_to_enemy = {name = "move_to_enemy", func = move_to_enemy},
        move_to_column_in_front_of_target = {name = "move_to_column_in_front_of_target", func = move_to_column_in_front_of_target},
        move_two_in_front_of_enemy = {name = "move_two_in_front_of_enemy", func = move_two_in_front_of_enemy},

        blade_dance = {name = "blade_dance", func = blade_dance},
        determine_blade_dance_move = {name = "", func = determine_blade_dance_move},
        determine_sword_type = {name = "", func = determine_sword_type}, 
        determine_gun_type = {name = "", func = determine_gun_type}, -- If above 50%, do gunblade, else gunsword PA

        block = {name = "block", func = block},
        longsword = {name = "longsword", func = longsword},
        deathwiper = {name = "deathwiper", func = deathwiper},

        block_or_sword = {name = "block_or_sword", func = block_or_sword},

        illusionary_force = {name = "illusionary_force", func = illusionary_force},

        rapier_thrust = {name = "rapier_thrust", func = rapier_thrust},

        silver_platter = {name = "silver_platter", func = silver_platter},
        gunblade = {name = "gunblade", func = gunblade},
        fumo_throw = {name = "fumo_throw", func = fumo_throw},
        gunsword_pa = {name = "gunsword_pa", func = gunsword_pa}
        
    }
    
    local s = self.states

    self.pattern = {
        s.idle, s.move, s.idle, 
        
        s.start_sub_pattern,
            s.move_to_front, s.block_or_sword,
        s.finish_sub_pattern,

        s.move, s.idle, 

        s.start_sub_pattern,
            s.determine_blade_dance_move,
            s.blade_dance,
        s.finish_sub_pattern,

        s.start_optional,
            s.block,
        s.finish_optional,

        s.idle, 

        s.skip_if_above_x, 
            s.start_sub_pattern,
                s.move_line_up,
                s.longsword,
                s.move_line_up,
                s.longsword,
                s.determine_blade_dance_move,
                s.blade_dance,
            s.finish_sub_pattern,
        s.finish_skip,


        s.start_sub_pattern, 
            s.move_to_back,
            s.silver_platter,
        s.finish_sub_pattern,
        
        s.gunblade,

        s.start_sub_pattern,
            s.move_to_front, 

            s.start_optional,
                s.block,
            s.finish_optional,

            s.determine_sword_type,
        s.finish_sub_pattern,

        s.move, s.idle, s.move, s.idle, 

        s.start_sub_pattern,
            s.move_to_front,
            s.rapier_thrust,
        s.finish_sub_pattern,

        s.move, s.fumo_throw,

        s.block, s.idle,
        
        s.start_sub_pattern, 
            s.move_to_front, 
            s.determine_sword_type,
        s.finish_sub_pattern,

        s.move, s.idle, s.move,

        s.illusionary_force,

        s.move, s.idle,

        s.skip_if_above_x, 

            s.start_sub_pattern,
                s.move_two_in_front_of_enemy, 
                s.gunsword_pa, 
            s.finish_sub_pattern,

            s.move, 
            s.idle,

        s.finish_skip,

        s.move, 

        s.start_optional,
            s.block,
            s.move,
        s.finish_optional,

        s.gunblade
    }



    self.pattern_index = 1
    self.in_sub_pattern = false
    self.in_possible_skip = false
    self.in_optional = false

    
    self.end_attack = false
    self.moving_to_enemy_tile = false
    --self.collision_available = true
    self.invincible = false
    
    self.idle_frames = 60
    self.idle_count = self.idle_frames -- Will count down to 0, loop back to s.idle_frames
    self.platters = {}

    self.first_act = true

    self.state_done = false

    self.state = self.pattern[1]

    self.first_flinch = true

    self.tiles_to_highlight = {}
    self.should_clear_highlight = true

    self.pandaemonium_check = Battle.Component.new(self, Lifetimes.Battlestep)

    self.pandaemonium_tiles = nil
    self.pandaemonium_parents = {}

    self.pandaemonium_check_frame = 0
    self.pandaemonium_attempted = false
    self.can_attempt_pandaemonium = false
    self.pandaemonium_check.update_func = function()
        if not self.can_attempt_pandaemonium and self.pandaemonium_tiles == nil and self:get_health() > 0 and self:get_health() <= self.max_health/2 then 
            self.can_attempt_pandaemonium = true
  
        end

        if self.can_attempt_pandaemonium and not self:is_sliding() and self.pandaemonium_check_frame % 2 == 1 then 
            self:card_action_event(pandaemonium_action(self), ActionOrder.Immediate)
            self.pandaemonium_attempted = true
        end

        if self.can_attempt_pandaemonium and not self:is_sliding() then  
            self.pandaemonium_check_frame = self.pandaemonium_check_frame + 1
        end
        
    end

    self:register_component(self.pandaemonium_check)


    anim:set_state("IDLE")
    anim:set_playback(Playback.Loop)    

    self.hit_func = function()
   --     print("Hit")
        self.flinching = false
        self.first_act = false
        self.state_done = false
        self.moving_to_enemy_tile = false
        if self.first_flinch then 
         --   self.state.cleanup
            self.last_state = self.state
            if self.state ~= self.states.idle and self.state ~= self.states.move then 
               -- increment_pattern(self)
            end

            self.first_flinch = false
        end
        self.state = self.states.flinch
        if self.slide_component ~= nil then 
          --  print("Hit while moving.")
            self.slide_component:eject()
            self.slide_component = nil
            self:set_offset(0, 0)

            if self.slide_dest and self:get_current_tile() ~= self.slide_dest then 
            --    print("Hit before reaching destination.")
                self:get_current_tile():remove_entity_by_id(self:get_id())
                self.slide_dest:add_entity(self)
                self.slide_dest = nil
            end

        end
        flinch(self)

    end

    self.delete_func = function(self)
        for i=1, #self.pandaemonium_parents
        do
            self.pandaemonium_parents[i]:delete()
        end

        self.pandaemonium_parents = {}
        self.update_func = function(self)
            anim:set_state("FLINCH")
            self.state = self.states.flinch
        end

    end


    self:register_status_callback(Hit.Stun, self.hit_func)
    self:register_status_callback(Hit.Flinch, self.hit_func)
    self:register_status_callback(Hit.Drag, self.hit_func)
    self:register_status_callback(Hit.Root, function() self.rooted = 120 end)

    -- Bring it back next build. For now, relying on the stun callback
    --[[
    self.on_countered = function(self)
        print("Countered")
        self:toggle_counter(false)
        self.hit_func(self)

    end
    --]]

    self.can_move_to_func = function(tile)
        if self.rooted > 0 then return false end
        if tile:is_edge() or not tile:is_walkable() then
            return false
        end
        if(tile:is_reserved({})) then
            return false
        end

        if not self.moving_to_enemy_tile and (tile:get_team() ~= self:get_team()) then
            return false
        end

        return not check_obstacles(tile, self) --and not check_characters(tile, self)
    end

    self.rooted = 0
    self.update_func = function(self)
      --  print("     ", self.state.name, self.moving_to_enemy_tile)
        if self.rooted > 0  then self.rooted = self.rooted - 1 end
        self.state.func(self)
     --   check_collision(self)
    end
    
end

function idle(self, time)
    if self.first_act then
        if anim:get_state() ~= "IDLE" then 
            anim:set_state("IDLE")
        end
        anim:set_playback(Playback.Loop)

        self.first_act = false
    end

    self.idle_count = self.idle_count - 1
    if self.idle_count == 0 then 
        self.idle_count = self.idle_frames
        self.state_done = false
        increment_pattern(self)
    end
end

function end_sub_pattern(self)
    while(self.in_sub_pattern)
    do
        increment_pattern(self)
    end

end

-- Probably need to do this better, in case there are skips inside skips. Same with end sub pattern having sub patterns inside
-- Also I didn't give it room to skip anything but 50% oops. The X isn't actually variable
function skip_pattern(self)
    while(self.in_possible_skip)
    do
        increment_pattern(self)
    end

end

function skip_optional(self)
    while(self.in_optional)
    do
        increment_pattern(self)
    end

end

function flinch(self)
   -- print("Flinch played")
    self.looped = false

    if not self.flinching then 
        anim:set_state("FLINCH")        
        anim:on_complete(function()
           -- print("Anim done")
            self.flinching = false
            self.state_done = true
            self.first_flinch = false

            self.looped = true

        --    print("Done")
            self.state_done = false
            if self.last_state ~= self.states.idle and self.last_state ~= self.states.move then 
           --     print("Last state was not idle or move")

                if not self.in_sub_pattern then 
                    increment_pattern(self)
                else
                    end_sub_pattern(self)
                end

               
            else
             --   print("Last state was idle or move")

                if self.in_sub_pattern then 
                    end_sub_pattern(self)
                else
                    self.state = self.last_state
                    self.first_act = true
                end
            end

        end)

    end

    self.flinching = true
   
end


function highlight_tiles(self, list, time)
    local spell = Battle.Spell.new(self:get_team())


    local ref = self
    spell.update_func = function(self)
        for i=1, #list
        do 
            local t = list[i]
            if t and not t:is_edge() then 
                t:highlight(Highlight.Solid)
            end

        end


        time = time - 1
        if time == 0 then 
            self:delete()
        end

        if self.flinching then 
            if spell and not spell:is_deleted() then 
                spell:delete()
    
            end
        end
    end


    self:get_field():spawn(spell, self:get_current_tile())

    return spell
end



function move(self)
    if self.first_act then 
        local tile = choose_move(self, self:get_field())
        if tile ~= self:get_current_tile() then
            anim:set_state("MOVE")
        else
            increment_pattern(self)
            return
        end
        anim:on_frame(2, function()
            if not self.can_move_to_func(tile) then 
                tile = self:get_current_tile()
            end
            self:teleport(tile, ActionOrder.Voluntary, nil)
        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end
end

function determine_blade_dance_move(self)
    if self:get_health() <= self.max_health/2 then 
        move_to_back(self)
    else
        move_near_front(self)
    end

end

function blade_dance(self)
    if self.first_act then 
        anim:set_state("BLADE_DANCE_1")
        local type = math.random(1, 2)
        local center_tile = nil -- Use if below half health instead of default t1
        local field = self:get_field()
        if self:get_health() <= self.max_health/2 then 
            local x = 3
            local y = math.random(1, field:height())
            if self:get_facing() == Direction.Right then 
                x = field:width() - 3
            end
            center_tile = field:tile_at(x, y)
        end
        anim:on_frame(1, function()
            local facing = self:get_facing()
            local t1 = self:get_tile(facing, 1)
            if center_tile then t1 = center_tile end
            self.tiles_to_highlight = {
                t1:get_tile(facing, 1), -- This is the double damage box
                t1:get_tile(Direction.Up, 1),
                t1:get_tile(Direction.Down, 1),
                t1:get_tile(Direction.join(facing, Direction.Up), 1),
                t1:get_tile(Direction.join(facing, Direction.Down), 1),
                t1:get_tile(facing, 2):get_tile(Direction.Down, 1),
            }

            if type == 1 then 
                table.insert(self.tiles_to_highlight, t1:get_tile(facing, 1):get_tile(Direction.Up, 2))
            else
                table.insert(self.tiles_to_highlight, t1:get_tile(facing, 2):get_tile(Direction.Up, 1))
            end

            highlight_tiles(self, self.tiles_to_highlight, 18)
        end)

        anim:on_frame(2, function()
            self:toggle_counter(true)
        end)

        anim:on_frame(4, function()
            self:toggle_counter(false)

            Engine.play_audio(SWORD_SFX, AudioPriority.Low)
            local facing = self:get_facing()
            local t1 = self:get_tile(facing, 1)
            if center_tile then t1 = center_tile end

            local tiles = {
                t1:get_tile(facing, 1), -- This is the double damage box
                t1:get_tile(Direction.Up, 1),
                t1:get_tile(Direction.Down, 1),
                t1:get_tile(Direction.join(facing, Direction.Up), 1),
                t1:get_tile(Direction.join(facing, Direction.Down), 1),
                t1:get_tile(facing, 2):get_tile(Direction.Down, 1),
            }

            if type == 1 then 
                table.insert(tiles, t1:get_tile(facing, 1):get_tile(Direction.Up, 2))
            else
                table.insert(tiles, t1:get_tile(facing, 2):get_tile(Direction.Up, 1))
            end
            
            blade_dance_attack(self, effect_texture, "effects.animation", self.damage, tiles, "BLADE_DANCE_SLASH_"..type)
            if self:get_health() <= self.max_health/2 then 
                create_sonic_boom(self, self:get_tile(facing, 1), effect_texture, "effects.animation", "BLADE_DANCE_SONIC_BOOM", 8, true)
            end


        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end

end

function determine_gun_type(self)
    if self:get_health() <= self.max_health/2 then 
        gunsword_pa(self)
    else
        gunblade(self)
    end
end

function block_or_sword(self)
    local r = math.random(0, 1)
    if r == 0 then 
        block(self)
        self.state = self.states.block
    else
        determine_sword_type(self)
    end
end

function determine_sword_type(self)
    local team = self:get_team()
    
    -- If no targets found, block
    -- If character same y and abs their x - self x <= 3, longsword
        -- Else if distance on abs y - self y <= 1 and abs their x - self x <= 2, deathwiper
        -- Else, blade dance if above 50%, else long sword
    -- Doing abs means it'll be weird if they're behind me, but if they're behind it doesn't matter anyway
    
    local list = self:get_field():find_nearest_characters(self, function(c)
        return c:get_team() ~= team 
    end)

    if #list == 0 then 
        self.state = self.states.longsword
        block(self)
        return
    end

    local target = list[1]
    local t = target:get_current_tile()
    local s_tile = self:get_current_tile()

    local dist_y = math.abs(t:y() - s_tile:y())
    local dist_x = math.abs(t:x() - s_tile:x())
    local below_half = self:get_health() <= self.max_health/2

    if dist_y == 0 and (below_half or dist_x <= 3) then 
        self.state = self.states.longsword
        longsword(self)
    elseif dist_y <= 1 and dist_x <= 2 then 
        self.state = self.states.deathwiper
        deathwiper(self)
    elseif not below_half then 
        self.state = self.states.blade_dance
        blade_dance(self)
    else
        self.state = self.states.longsword
        longsword(self)
    end
        

end

function block(self)
    if self.first_act then 
        anim:set_state("BLOCK")

        anim:on_frame(1, function()
            make_defense(self)
        end)

        anim:on_complete(function()
            if self.defense then 
                self:remove_defense_rule(self.defense)
                self.defense = nil
                self.defense_guarded = false
                self.defense_should_reflect = false
                self.reflect_comp:eject()
            end
            increment_pattern(self)
        
        end)

        self.first_act = false
    end

end

function create_sword(self, sword)
    self.sword_graphic = graphic_init("spell", 0, 0, swords_texture, "swords.animation", -1, "SWORD_"..sword, self, self:get_facing(), true)

    self:get_field():spawn(self.sword_graphic, self:get_current_tile())
end

function create_sonic_boom(self, tile, texture, animation, state, speed, should_pierce)
    if not tile or tile:is_edge() then return end

    local spell = graphic_init("spell", 0, 0, texture, animation, -1, state, self, self:get_facing())
    
    local hit_props = HitProps.new(
        self.damage,
        Hit.Impact | Hit.Bubble,
        Element.Sword, 
        self:get_id(), 
        Drag.None
    )

    spell:set_hit_props(hit_props)

    spell.update_func = function(self)
        self:get_current_tile():attack_entities(self)
        if self:is_sliding() == false then
            local dest = self:get_tile(self:get_facing(), 1)

            if dest == nil or (self:get_current_tile():is_edge() and self.slide_started) then 
                self:delete()
                return

            end 
            
            self:slide(dest, frames(speed), frames(0), ActionOrder.Voluntary,
                function()
                    self.slide_started = true 
                end
            )
        end
    end

    spell.can_move_to_func = function()
        return true
    end

    spell.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)
    end

    spell.collision_func = function(self, other)
        self:get_field():spawn(graphic_init("artifact", 0, -10, guard_texture, "guard.animation", -6, "NORMAL_HIT", self, facing, true), self:get_current_tile())
        if not should_pierce then self:delete() end
    end

    self:get_field():spawn(spell, tile)
end

function longsword(self)
    if self.first_act then 
        anim:set_state("SWING")

        anim:on_frame(1, function()
            self:toggle_counter(true)

            self.tiles_to_highlight = {
                self:get_tile(self:get_facing(), 1),
                self:get_tile(self:get_facing(), 2),
                self:get_tile(self:get_facing(), 3),
            }

            highlight_tiles(self, self.tiles_to_highlight, 12)
        
        end)
    
        anim:on_frame(3, function()
            self:toggle_counter(false)

            create_sword(self, 1)
            Engine.play_audio(SWORD_SFX, AudioPriority.Low)
            longsword_attack(self, "FIGHTER")

            if self:get_health() <= self.max_health/2 then 
                create_sonic_boom(self, self:get_tile(self:get_facing(), 2), slashes_texture, "slashes.animation", "SONIC_BOOM", 8)
            end
        end)

  
        anim:on_complete(function()
            if self.sword_graphic and not self.sword_graphic:is_deleted() then 
                self.sword_graphic:delete()
                self.sword_graphic = nil
            end
            increment_pattern(self)
        
        end)

        -- Doesn't really do anything, since we set interrupt in longsword_attack
        anim:on_interrupt(function()
            if self.sword_graphic and not self.sword_graphic:is_deleted() then 
                self.sword_graphic:delete()
                self.sword_graphic = nil
            end
        end)

        self.first_act = false
    end

end

function deathwiper(self)
    if self.first_act then 
        anim:set_state("SWING")

        anim:on_frame(1, function()
            self:toggle_counter(true)

            local tile = self:get_tile(self:get_facing(), 1)
            self.tiles_to_highlight = {
                tile,
                tile:get_tile(self:get_facing(), 1),
                tile:get_tile(Direction.Up, 1),
                tile:get_tile(Direction.Down, 1),
                tile:get_tile(Direction.join(Direction.Up, self:get_facing()), 1),
                tile:get_tile(Direction.join(Direction.Down, self:get_facing()), 1)
            }

            highlight_tiles(self, self.tiles_to_highlight, 12)
        
        end)

        anim:on_frame(3, function()
            self:toggle_counter(false)

            create_sword(self, 1)
            Engine.play_audio(SWORD_SFX, AudioPriority.Low)
            longsword_attack(self, "DEATHWIPER")
        end)

  
        anim:on_complete(function()
            if self.sword_graphic and not self.sword_graphic:is_deleted() then 
                self.sword_graphic:delete()
                self.sword_graphic = nil
            end
            increment_pattern(self)
        
        end)

        self.first_act = false
    end

end

function sword_attack(user, id)
    local field = user:get_field()
    local facing = user:get_facing()
    local function create_hitbox(player, tile, spell_list, reflect_list, moving)
        moving = moving or false
        if not tile or tile:is_edge() then 
            return
        end

        local spell = nil
        if not moving then 
            spell = Battle.Spell.new(player:get_team())
        else
            spell = graphic_init("spell", 0, 0, slashes_texture, "slashes.animation", -5, "SONIC_BOOM", player, facing)

        end
        local poison = false
        local damage = player.damage
        local flags = Hit.Impact | Hit.Bubble
        local element = Element.Sword

        local hit_props = HitProps.new(
            damage,
            flags,
            element, 
            id, 
            Drag.None
        )

        spell:set_hit_props(hit_props)
        spell:set_facing(facing)
        
        local lifetime = 9
        spell.has_hit = false

        spell.update_func = function(self, dt)
            local can_hit = true

            for i=1, #spell_list
            do
                if not spell_list[i]:is_deleted() then 
                    can_hit = can_hit and not spell_list[i].has_hit
                    if not can_hit then 
                        break
                    end
                else
                    can_hit = false
                    break
                end
            end


            lifetime = lifetime - 1
            if lifetime == 0 then 
                self:delete()
            end

            if not can_hit then 
                return 
            end
        
            self:get_tile():attack_entities(self)

        end

        spell:set_float_shoe(true)
       

        spell.delete_func = function()
            if not moving and reflect_list then 
                for i=1, #reflect_list
                do
                    if not reflect_list[i]:is_deleted() then 
                        reflect_list[i]:delete()
                    end

                end
            end
        end


        spell.attack_func = function(self, other)
            Engine.play_audio(HIT_SFX, AudioPriority.Low)

        end

        spell.collision_func = function(self, other)
            self.has_hit = true
            if moving then 
                field:spawn(graphic_init("artifact", 0, 0, guard_texture, "guard.animation", -6, "NORMAL_HIT", player, facing, true), self:get_current_tile())
                self:delete()
            end
        end

        if not moving then 
            table.insert(spell_list, spell)
        end

        field:spawn(spell, tile)
    end


    local tile = user:get_tile(user:get_facing(), 1)
    local facing = user:get_facing()
    local slash = graphic_init("spell", 0, 0, slashes_texture, "slashes.animation", -5, "KNIFE", user, facing, true)

    local list = {}
    local list2 = {}

    local tiles = {
        tile,
        tile:get_tile(user:get_facing(), 1),
    }

    create_hitbox(user, tiles[1], list)


    field:spawn(slash, tile)

end

function widesword_attack(self, element, damage)
    local state = "1"
    if element == Element.Wood then 
        state = "2"
    elseif element == Element.Aqua then 
        state = "3"
    elseif element == Element.Elec then 
        state = "4"
    elseif element == "Element.Poison" then 
        element = nil
        state = "5"
    end

    local t = self:get_tile(self:get_facing(), 1)
    local tiles = {
        t,
        t:get_tile(Direction.Up, 1),
        t:get_tile(Direction.Down, 1)
    }
    
    blade_dance_attack(self, slashes_texture, "slashes.animation", damage, tiles, "SWORD_"..state, element)

    anim:on_interrupt(function()
        if self.sword_graphic and not self.sword_graphic:is_deleted() then 
            self.sword_graphic:delete()
            self.sword_graphic = nil
        end
    end)
end

function longsword_attack(user, state)
    local field = user:get_field()
    local facing = user:get_facing()
    local function create_hitbox(player, tile, spell_list, reflect_list, moving)
        moving = moving or false
        if not tile or tile:is_edge() then 
            return
        end

        local spell = nil
        if not moving then 
            spell = Battle.Spell.new(player:get_team())
        else
            spell = graphic_init("spell", 0, 0, slashes_texture, "slashes.animation", -5, "SONIC_BOOM", player, facing)

        end
        local poison = false
        local damage = player.damage
        local flags = Hit.Bubble
        local element = Element.Sword
      --  local sword = player.ready_sword

        local should_heal = false
        local should_poison = false
    
        local hit_props = HitProps.new(
            damage,
            flags,
            element, 
            player:get_id(), 
            Drag.None
        )

        spell:set_hit_props(hit_props)
        spell:set_facing(facing)
        
        local lifetime = 9
        spell.has_hit = false

        spell.update_func = function(self, dt)
            local can_hit = true
            --self:get_tile():highlight(Highlight.Solid)

            if not moving then 
                for i=1, #spell_list
                do
                    if not spell_list[i]:is_deleted() then 
                        can_hit = can_hit and not spell_list[i].has_hit
                        if not can_hit then 
                            break
                        end
                    else
                        can_hit = false
                        break
                    end
                end


                lifetime = lifetime - 1
                if lifetime == 0 then 
                    self:delete()
                end

                if not can_hit then 
                    return 
                end
            else
                if self:is_sliding() == false then
                    if self:get_current_tile():is_edge() and self.slide_started then 
                        self:delete()
        
                    end 
                    
                    local dest = self:get_tile(facing, 1)
                    self:slide(dest, frames(5), frames(0), ActionOrder.Voluntary,
                        function()
                            self.slide_started = true 
                        end
                    )
                end

            end


            self:get_tile():attack_entities(self)
    
        end

        spell:set_float_shoe(true)
        spell.can_move_to_func = function()
            return true
        end

    
        spell.can_move_to_func = function(tile)
            return true
        end
    
        spell.attack_func = function(self, other)
            Engine.play_audio(HIT_SFX, AudioPriority.Low)

        end
    
        spell.collision_func = function(self, other)
            self.has_hit = true
            if moving then 
                field:spawn(graphic_init("artifact", 0, 0, guard_texture, "guard.animation", -6, "NORMAL_HIT", player, facing, true), self:get_current_tile())
                self:delete()
            end
        end

        if not moving then 
            table.insert(spell_list, spell)
        end

        field:spawn(spell, tile)
    end


    local tile = user:get_tile(user:get_facing(), 1)
    local facing = user:get_facing()
    local slash = graphic_init("spell", 0, 0, slashes_texture, "slashes.animation", -5, state, user, facing, true)

    local list = {}
    local list2 = {}

    local tiles = {
        tile,
        tile:get_tile(user:get_facing(), 1),
        tile:get_tile(user:get_facing(), 2)
    }

    if state == "DEATHWIPER" then 
        tiles = {
            tile,
            tile:get_tile(user:get_facing(), 1),
            tile:get_tile(Direction.Up, 1),
            tile:get_tile(Direction.Down, 1),
            tile:get_tile(Direction.join(Direction.Up, user:get_facing()), 1),
            tile:get_tile(Direction.join(Direction.Down, user:get_facing()), 1)
        }
    end

    for i=1, #tiles
    do
        create_hitbox(user, tiles[i], list)

    end

    user:get_animation():on_interrupt(function()
        if user.sword_graphic and not user.sword_graphic:is_deleted() then 
            user.sword_graphic:delete()
            user.sword_graphic = nil
        end

        for i=1, #list
        do
            local s = list[i]
            if s and not s:is_deleted() then 
                s:delete()
            end
        end
    end)

    field:spawn(slash, tile)
end

-- Third copy of this function. Go make a real generic sword call
-- Now I have the presence or absence of element determining if this should do doubled damage on tiles[i]
    -- Reuse for Wide, but bad and I should just make a better function
function blade_dance_attack(user, texture, animation, damage, tiles, anim_state, element)
    local field = user:get_field()
    local facing = user:get_facing()
    local function create_hitbox(player, tile, spell_list, damage)
        moving = moving or false
        if not tile or tile:is_edge() then 
            return
        end

        local spell = Battle.Spell.new(player:get_team())
        local flags = Hit.Impact | Hit.Flinch | Hit.Flash
        if element == nil then 
            element = Element.Sword
        elseif element == "Element.Poison" then 
            spell:set_name(element)
            element = Element.Null
        end

        local hit_props = HitProps.new(
            damage,
            flags,
            element, 
            player:get_id(), 
            Drag.None
        )

        spell:set_hit_props(hit_props)
        spell:set_facing(facing)
        
        local lifetime = 7
        spell.has_hit = false

        spell.update_func = function(self, dt)
            local can_hit = true

            for i=1, #spell_list
            do
                if not spell_list[i]:is_deleted() then 
                    can_hit = can_hit and not spell_list[i].has_hit
                    if not can_hit then 
                        break
                    end
                else
                    can_hit = false
                    break
                end
            end


            lifetime = lifetime - 1
            if lifetime == 0 then 
                self:delete()
            end

            if not can_hit then 
                return 
            end


            self:get_tile():attack_entities(self)
        end

        spell:set_float_shoe(true)
    
        spell.attack_func = function(self, other)
            Engine.play_audio(HIT_SFX, AudioPriority.Low)

        end
    
        spell.collision_func = function(self, other)
            self.has_hit = true
            if moving then 
                field:spawn(graphic_init("artifact", 0, 0, guard_texture, "guard.animation", -6, "NORMAL_HIT", player, facing, true), self:get_current_tile())
                self:delete()
            end
        end

        if not moving then 
            table.insert(spell_list, spell)
        end

        field:spawn(spell, tile)
    end


    local facing = user:get_facing()
    local slash = graphic_init("spell", 0, 0, texture, animation, -2, anim_state, user, facing, true)

    local list = {}

    if element == nil then 
        create_hitbox(user, tiles[1], list, damage*2)
    end

    local iter_start = 2
    if element ~= nil then 
        iter_start = 1
    end

    for i=2, #tiles
    do
        create_hitbox(user, tiles[i], list, damage)

    end

    
    field:spawn(slash, tiles[1])
end


function reflect_attack(user, id)
    local spell = Battle.Spell.new(user:get_team())
    spell:set_facing(user:get_facing())
    local facing = spell:get_facing()
    local field = user:get_field()

    local function create_effect()
        local spell2 = graphic_init("spell", 0, 0, guard_texture, "guard.animation", -3, "REFLECT", spell, spell:get_facing(), true)

    end

    local hit_props = HitProps.new(
        user.damage,
        Hit.Impact | Hit.Flinch | Hit.Flash,
        Element.None, 
        id, 
        Drag.None
    )

    spell:set_hit_props(hit_props)

    spell.update_func = function(self)
        self:get_tile():attack_entities(self)
        if self:is_sliding() == false then
            if self:get_current_tile():is_edge() and self.slide_started then 
                self:delete()
            end 
            
            local dest = self:get_tile(facing, 1)
            local ref = self
            self:slide(dest, frames(0), frames(0), ActionOrder.Voluntary, 
                function()                           
                    ref.slide_started = true 
                    field:spawn(graphic_init("spell", 0, 0, guard_texture, "guard.animation", -3, "REFLECT", spell, spell:get_facing(), true), ref:get_current_tile())
                end)
        end
    end

    spell.can_move_to_func = function()
        return true
    end

    spell.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)

    end


    field:spawn(spell, user:get_current_tile())
end

function make_defense(self)
    self.defense = Battle.DefenseRule.new(224,DefenseOrder.CollisionOnly)
    self.defense_guarded = false
    self.defense_should_reflect = false
    local has_reflected = false
    local reflector = self
    local comp = Battle.Component.new(reflector, Lifetimes.Local)
    comp.update_func = function()
        if reflector.defense_should_reflect and not has_reflected then 
            reflect_attack(reflector, self:get_id())
            reflector.defense_should_reflect = false
            has_reflected = true
        end

    end

    self.reflect_comp = comp

    self:register_component(self.reflect_comp)

    self.defense.can_block_func = function(judge, attacker, defender)
        local attacker_hit_props = attacker:copy_hit_props()
        if attacker_hit_props.damage > 0 and (attacker_hit_props.flags & ~Hit.Breaking == attacker_hit_props.flags) then
            judge:block_impact()
            judge:block_damage()

            self.defense_guarded = true
            self.defense_should_reflect = true

            defender:get_field():spawn(graphic_init("artifact", 0, -32, guard_texture, "guard.animation", -4, "GUARD", defender, defender:get_facing(), true), defender:get_current_tile())
            Engine.play_audio(GUARD_SFX, AudioPriority.Low)


        end


    end

    self:add_defense_rule(self.defense)

end

function unused_blade_dance_attack(self, tiles)
    local hit_props = HitProps.new(
        self.damage,
        Hit.Impact | Hit.Flinch | Hit.Flash,
        Element.Sword, 
        self:get_id(), 
        Drag.None
    )

    for i=2, #tiles
    do
        local t = tiles[i]
        if t and not t:is_edge() then 
            create_hitbox(self, hit_props, t, 12)
        end
    end

    hit_props.damage = hit_props.damage*2
    create_hitbox(self, hit_props, tiles[1], 6)

    local slash = graphic_init("artifact", 0, 0, effect_texture, "effects.animation", -3, "BLADE_DANCE_SLASH_1", self, self:get_facing(), true)
    self:get_field():spawn(slash, tiles[1])
end

function create_hitbox(self, props, tile, lifetime, should_highlight)
    local spell = Battle.Spell.new(self:get_team())
    if should_highlight then 
        spell:highlight_tile(Highlight.Solid)
    end
    spell:set_hit_props(props)

    spell.lifetime = lifetime

    spell.update_func = function(self)
        self:get_current_tile():attack_entities(self)
        self.lifetime = self.lifetime - 1
        if self.lifetime == 0 then 
            self:delete()
        end

    end

    spell.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)

    end

    self:get_field():spawn(spell, tile)
end


function call_sprite(self, list, num)
    if not list then return end
    if num > #list then return end
    local target = self:get_field():find_nearest_characters(self, function(c)
        return c:get_team() ~= self:get_team()
    end)

    local pivot = nil
    if target[1] then 
        pivot = target[1]:get_current_tile()
    end

    local facing = self:get_facing()
    local t = list[num]
    if pivot then 
        local dif = t:x() - pivot:x()
        if dif < 0 then 
            facing = self:get_facing_away()
        end

        if t:get_tile(facing, 1):is_edge() then
            facing = Direction.reverse(facing)
        end
    end

    spawn_sprite(self, t, facing)
end


function spawn_sprite(self, tile, facing)
    local spell = graphic_init("spell", 0, 0, effect_texture, "effects.animation", -1, "ILLUSIONARY_SPRITE", self, facing, true)
    tile:reserve_entity_by_id(spell:get_id())
    local anim = spell:get_animation()
    spell.should_collide = false

    spell.damage = self.damage
    local hit_props = HitProps.new(
        spell.damage,
        Hit.Impact | Hit.Flinch | Hit.Flash,
        Element.None, 
        self:get_id(), 
        Drag.None
    )

    spell:set_hit_props(hit_props)

    spell.on_spawn_func = function()
        Engine.play_audio(DARK_SFX, AudioPriority.Low)

    end

    spell.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)
    end

    anim:on_frame(8, function()
        spell.should_collide = true
        local list = {
            spell:get_tile(spell:get_facing(), 1)
        } 
        if self:get_health() <= self.max_health/2 then 
            table.insert(list, spell:get_tile(Direction.join(spell:get_facing(), Direction.Up), 1))
            table.insert(list, spell:get_tile(Direction.join(spell:get_facing(), Direction.Down), 1))
        end
        highlight_tiles(spell, list, 12)

    end)

    anim:on_frame(11, function()
        if self:get_health() <= self.max_health/2 then 
            widesword_attack(spell, Element.Sword, self.damage)
            self:get_field():spawn(graphic_init("spell", 0, 0, slashes_texture, "slashes.animation", -2, "SWORD_1", spell, spell:get_facing(), true), spell:get_tile(spell:get_facing(), 1))
        else
            sword_attack(spell, self:get_id())
            self:get_field():spawn(graphic_init("spell", 0, 0, slashes_texture, "slashes.animation", -2, "KNIFE", spell, spell:get_facing(), true), spell:get_tile(spell:get_facing(), 1))

        end
        Engine.play_audio(SWORD_SFX, AudioPriority.Low)
    end)

    anim:on_frame(16, function()
        spell.should_collide = false
    end)

    spell.update_func = function(self)
        if self.should_collide then 
            self:get_current_tile():attack_entities(self)
        end

    end

    self:get_field():spawn(spell, tile)
end

function illusionary_force(self)
    if self.first_act then 
        anim:set_state("ILLUSIONARY_FORCE")
        local list = {}
        local n = 1
        local f = self:get_field()
        local hide_tile = self:get_field():tile_at(f:width()+1, f:height()+1)
        local is_wide = math.random(0, 1)

        anim:on_frame(2, function()
            Engine.play_audio(DARK_SFX, AudioPriority.Low)
        end)


        anim:on_frame(3, function()
            -- Do something with defense above
            self.invincible = true
            self:toggle_hitbox(false)
        
        end)

        anim:on_frame(24, function()
            self:toggle_hitbox(true)
            self.invincible = false
        end)
    
        anim:on_frame(11, function()
            list = choose_n_random_enemy_tiles_no_objects(self, 3)
            local t = self:get_current_tile()
            t:remove_entity_by_id(self:get_id())
            hide_tile:add_entity(self)

        end)

        for i=12, 14
        do
            anim:on_frame(i, function()
                call_sprite(self, list, n)
                n = n+1
            end)
        end

        anim:on_frame(16, function()
            local t = choose_enemy(self, self:get_field())
            self.moving_to_enemy_tile = true
            if not self.can_move_to_func(t) then 
             --   print("Couldn't move there")
                self.moving_to_enemy_tile = false
                t = choose_move(self, self:get_field())
                if not t then return end
            end
            self.moving_to_enemy_tile = false

            Engine.play_audio(DARK_SFX, AudioPriority.Low)
            hide_tile:remove_entity_by_id(self:get_id())
            t:add_entity(self)

        end)

        anim:on_frame(25, function()
            local facing = self:get_facing()
            local t = self:get_tile(facing, 1)
            self.tiles_to_highlight = {
                t,
                self:get_tile(facing, 2),
                self:get_tile(facing, 3)

            }
            if is_wide == 1 then 
                self.tiles_to_highlight = {
                    t,
                    t:get_tile(Direction.Up, 1),
                    t:get_tile(Direction.Down, 1)
    
                }
            end

            highlight_tiles(self, self.tiles_to_highlight, 12)
        
        end)

        anim:on_frame(27, function()
            if not self:get_current_tile() or self:get_current_tile():is_edge() then return end
            create_sword(self, 1)
            Engine.play_audio(SWORD_SFX, AudioPriority.Low)

            
            if is_wide == 0 then 
                longsword_attack(self, "FIGHTER")
                return
            end

            -- I did this in the worst way possible, now suffer
                -- I have widesword_attach check element as an actual element
                    -- Should've had it check number then assign element there to be less messy here

            local r = math.random(1, 4)
            local ele = nil

            if r == 4 then 
                ele = Element.Elec
            elseif r == 3 then 
                ele = Element.Aqua
            elseif r == 2 then 
                ele = Element.Wood
            elseif r == 1 then 
                ele = Element.Sword
            end

            widesword_attack(self, ele, self.damage)
        end)
  
        anim:on_complete(function()
            if self.sword_graphic and not self.sword_graphic:is_deleted() then 
                self.sword_graphic:delete()
                self.sword_graphic = nil
            end
            increment_pattern(self)
        
        end)

        self.first_act = false
    end

end

function create_rapier_projectile(self, tile, speed, is_bullet)
    if not tile or tile:is_edge() then return end
    local state = "RAPIER_STAB"
    local flags = Hit.Impact | Hit.Flinch
    local element = Element.Sword

    if is_bullet then 
        state = "RAPIER_BULLET"
        flags = flags | Hit.Breaking | Hit.Flash
        element = Element.None
    end

    local spell = graphic_init("spell", 0, 0, effect_texture, "effects.animation", -1, state, self, self:get_facing(), (not is_bullet))

    spell.damage = self.damage

    local hit_props = HitProps.new(
        spell.damage,
        flags,
        element, 
        self:get_id(), 
        Drag.None
    )

    spell:set_hit_props(hit_props)


    spell.update_func = function(self)
        self:get_current_tile():attack_entities(self)
        if self:is_sliding() == false and (not self.slide_started or is_bullet) then
            local dest = self:get_tile(self:get_facing(), 1)

            if dest == nil or (self:get_current_tile():is_edge() and self.slide_started) then 
                self:delete()
                return

            end 
            
            self:slide(dest, frames(speed), frames(0), ActionOrder.Voluntary,
                function()
                    self.slide_started = true 
                end
            )
        end
    end

    spell.can_move_to_func = function()
        return true
    end

    spell.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)
    end
    
    self:get_field():spawn(spell, tile)
end

function rapier_thrust(self)
    if self.first_act then 
        local tiles = {}
        local random = nil
        local speed = 6
        local function gather_tiles()
            local f = self:get_facing()
            local t = self:get_tile(f, 1)

            local maybe_tiles = {
                t:get_tile(Direction.Up, 1),
                t:get_tile(Direction.Down, 1),
                t:get_tile(f, 1),
                t:get_tile(Direction.join(f, Direction.Up), 1),
                t:get_tile(Direction.join(f, Direction.Down), 1)

            }

            for i=1, #maybe_tiles
            do
                if maybe_tiles[1] and not maybe_tiles[i]:is_edge() then 
                    table.insert(tiles, maybe_tiles[i])
                end
            end
            
        end

        gather_tiles()

        local state = "RAPIER_THRUST"
        
        local below_half = self:get_health() <= self.max_health/2 
        local max = 13
        if below_half then 
            state = "RAPIER_THRUST_2"
            max = 25
        end
        
        anim:set_state(state)

        local props = HitProps.new(
            math.floor(self.damage/2),
            Hit.Impact | Hit.Flinch,
            Element.Sword, 
            self:get_id(), 
            Drag.None
        )

        anim:on_frame(1, function()
            self:toggle_counter(true)

        end)

        anim:on_frame(3, function()
            self:toggle_counter(false)

        end)

        for i=3, max, 2
        do
            anim:on_frame(i, function()
                create_hitbox(self, props, self:get_tile(self:get_facing(), 1), speed, false)
                Engine.play_audio(SWORD_SFX, AudioPriority.Low)

            end)
        end
        --30
        
        for i=4, max, 3
        do
            anim:on_frame(i-3, function()
                gather_tiles()
                if #tiles == 0 then return end
                random = math.random(#tiles)
    
                highlight_tiles(self, {tiles[random]}, 18)
    
            end)

            anim:on_frame(i, function()
                gather_tiles()
                if #tiles == 0 then return end

                create_rapier_projectile(self, tiles[random], 6)
            end)
        end

        local max2 = 18
        if below_half then 
            max2 = 30
        end
        anim:on_frame(max2, function()
            Engine.play_audio(SHOOT_SFX, AudioPriority.Low)
            create_rapier_projectile(self, self:get_tile(self:get_facing(), 1), 12, true)
        end)


        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end

end

function silver_platter(self)
    if self.first_act then 
        anim:set_state("SILVER_PLATTER")

        anim:on_frame(1, function()
            Engine.play_audio(APPEAR_SFX, AudioPriority.Low)
            for i=1, #self.platters
            do
                if not self.platters[i]:is_deleted() then 
                    self.platters[i].remove = true

                end
            end

            self.platters = {}

            spawn_platters(self)
        end)

        anim:on_frame(7, function()
            Engine.play_audio(DASH_SFX, AudioPriority.Low)
            for i=1, #self.platters
            do
                self.platters[i].moving = true
            end
            
        end)
  
        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end

end

function create_shot(self, tile)
    local spell = graphic_init("spell", 0, 0, effect_texture, "effects.animation", -1, "SHOT", self, self:get_facing(), true)

    local props = HitProps.new(
        self.damage,
        Hit.Impact | Hit.Flinch | Hit.Flash,
        Element.None, 
        self:get_id(), 
        Drag.None
    )

    spell:set_hit_props(props)

    spell.attacking = true
    spell:get_animation():on_frame(2, function()
        spell.attacking = false
    end)

    spell.update_func = function(self)
        if self.attacking then 
            self:get_current_tile():attack_entities(self)
        end
    
    end

    spell.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)

    end

    spell.on_spawn_func = function()
        Engine.play_audio(SHOOT_SFX, AudioPriority.Low)
    end
    self:get_field():spawn(spell, tile)
end

function gunblade(self)
    if self.first_act then 
        local state = "GUNBLADE"
        local below_half = self:get_health() <= self.max_health/2
        if below_half then state = "GUNBLADE_2" end
        anim:set_state(state)
 
        local tiles = self:get_field():find_tiles(function(t)
            return not t:is_edge() and t:get_team() ~= self:get_team()
        end)

        anim:on_frame(1, function()
            self:toggle_counter(true)

        end)

        anim:on_frame(5, function()
            self:toggle_counter(false)

        end)

        local tile = nil
        local tile2

        if below_half then
            for i=7, 35, 7
            do
                anim:on_frame(i-3, function()
                    local r = math.random(1, #tiles)
                    tile = table.remove(tiles, r)
                    tile2 = tiles[math.random(1, #tiles)]
                    table.insert(tiles, tile)
                    
                    highlight_tiles(self, {tile}, 18)
                    highlight_tiles(self, {tile2}, 24)

                end)

                anim:on_frame(i-1, function()
                    create_shot(self, tile)
                end)

                anim:on_frame(i, function()
                    create_shot(self, tile2)
                end)
            end
        else

            for i=6, 22, 4
            do
                anim:on_frame(i-3, function()
                    tile = tiles[math.random(1, #tiles)]
                    highlight_tiles(self, {tile}, 18)
                end)

                anim:on_frame(i, function()
                    create_shot(self, tile)
                end)
            end
        end
  
        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end

end

-- Tries to spawn three platters 
-- If on top or bottom of column, will extend down or up
function spawn_platters(self)
    local facing = self:get_facing()
    local tiles = {}
    local tile = self:get_tile(facing, 1)
    table.insert(tiles, tile)
    if self:get_tile(Direction.Up, 1):is_edge() then 
        table.insert(tiles, tile:get_tile(Direction.Down, 2))
    else
        table.insert(tiles, tile:get_tile(Direction.Up, 1))
    end

    if self:get_tile(Direction.Down, 1):is_edge() then 
        table.insert(tiles, tile:get_tile(Direction.Up, 2))
    else
        table.insert(tiles, tile:get_tile(Direction.Down, 1))
    end

    for i=1, #tiles 
    do
        make_platter(self, tiles[i])
    end

end

function bomb_explosion(self, tile)
    if not tile or tile:is_edge() then return end
    local spell = graphic_init("spell", 0, 0, effect_texture, "effects.animation", -1, "EXPLOSION", self, self:get_facing(), true)

    local hit_props = HitProps.new(
        self.damage,
        Hit.Impact | Hit.Flinch | Hit.Flash,
        Element.None, 
        self:get_id(), 
        Drag.None
    )

    spell:set_hit_props(hit_props)

    spell.update_func = function(self)
        self:get_tile():attack_entities(self)
    end


    spell.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)
    end

    self:get_field():spawn(spell, tile)
end

function explode_bomb(bomb, bomb_type, self)
    local tiles = {}
    if bomb_type == 1 then 
        tiles = {
            bomb:get_tile(),
            bomb:get_tile(Direction.Right, 1),
            bomb:get_tile(Direction.Left, 1),
        }
    else
        tiles = {
            bomb:get_tile(),
            bomb:get_tile(Direction.Up, 1),
            bomb:get_tile(Direction.Down, 1),
        }
    end

    bomb:hide()
    bomb:toggle_hitbox(false)
    bomb:delete()

    for i=1, #tiles
    do
        bomb_explosion(self, tiles[i])
    end

    Engine.play_audio(EXPLOSION_SFX, AudioPriority.Low)
end

function make_platter(self, tile)
    if not tile or tile:is_edge() or not tile:is_walkable() then return end
    local platter = graphic_init("obstacle", 0, 0, effect_texture, "effects.animation", 1, "CLOSED_PLATTER", self, self:get_facing())
    local collision = Battle.Spell.new(self:get_team())

    local hit_props = HitProps.new(
        self.damage,
        Hit.Impact | Hit.Flinch | Hit.Flash,
        Element.None, 
        self:get_id(), 
        Drag.None
    )

    collision:set_hit_props(hit_props)

    collision.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)
    end

    platter:share_tile(true)
    platter:set_health(200)
    platter:set_team(Team.Other)
    platter.defense_should_remove = false
    platter.lid_removed = false
    platter.should_explode = false
    platter.remove = false
    platter:set_float_shoe(true)


    local field = self:get_field()
    platter.can_move_to_func = function(tile)
        if tile:is_edge() or not tile:is_walkable() then
            return false
        end
        
        return not check_obstacles(tile, platter)
    end

    local defense = Battle.DefenseRule.new(224,DefenseOrder.CollisionOnly)
    
    local comp = Battle.Component.new(platter, Lifetimes.Local)
    local lid_facing = nil
    local bomb_type = math.random(1, 2)
    comp.update_func = function()
        if platter.defense_should_remove and not platter.lid_removed then 
            local off = platter:get_tile_offset()
            field:spawn(graphic_init("artifact", off.x*-2, 0, effect_texture, "effects.animation", -4, "PLATTER_LID", platter, lid_facing, true), platter:get_current_tile())
            Engine.play_audio(GUARD_SFX, AudioPriority.Low)
            platter:get_animation():set_state("BOMB_"..bomb_type)
            platter:get_animation():on_complete(function()
                explode_bomb(platter, bomb_type, self)
            end)

            platter:get_animation():refresh(platter:sprite())
            platter.lid_removed = true
        end

        if platter.should_explode then 
            explode_bomb(platter, bomb_type, self)
        end

        if not platter.lid_removed and platter.remove then 
            platter:get_animation():set_state("CLOSED_PLATTER_REVERSE")
            platter:get_animation():on_complete(function()
                platter:delete()
            end)

            comp:eject()
        end
    end

    platter.delete_func = function()
        collision:delete()
        if platter:get_health() == 0 then 
            bomb_explosion(self, platter:get_current_tile())
        end
    end

    collision.can_move_to_func = function(tile)
        if tile:is_edge() or not tile:is_walkable() then
            return false
        end
        
        return not check_obstacles(tile, platter)
    end

    

    platter:register_component(comp)

    defense.can_block_func = function(judge, attacker, defender)
        local attacker_hit_props = attacker:copy_hit_props()
        if attacker:get_id() == collision:get_id() then 
            judge:block_damage()
            judge:block_impact()
            return 
        end
        if not platter.lid_removed then 
            judge:block_damage()

        else
            if attacker_hit_props.element == Element.Fire then 
                platter.should_explode = true
            end

            return
        end
        platter.defense_should_remove = true
        if attacker and attacker:get_facing() then
            lid_facing = attacker:get_facing()
        else
            lid_facing = platter:get_facing()
        end
    end

    platter:add_defense_rule(defense)

    platter.moving = false
    local found_target = false
    local stopped = false
    
    local tiles = {
        tile:get_tile(Direction.Up, 1),
        tile:get_tile(Direction.Down, 1),
        tile:get_tile(platter:get_facing(), 1)
    }

    platter.update_func = function(self)
        if not self.moving then return end
        tile = self:get_tile()
        tiles = {
            tile:get_tile(Direction.Up, 1),
            tile:get_tile(Direction.Down, 1),
            tile:get_tile(platter:get_facing(), 1)
        }

        if not found_target then 
            for i=1, #tiles
            do
                local chars = tiles[i]:find_characters(function(c)
                    return c:get_team() ~= self:get_team()
                end)
                if #chars > 0 then 
                    found_target = true
                    break
                end
            end

            if check_obstacles(tiles[3], self) then 
                found_target = true
            end
        end

        if found_target then 
            if self:is_sliding() == false then 
                self:share_tile(false)
                self.update_func = function()
                    platter:get_tile():attack_entities(collision)
                end
            end
            
            return
        end

        if not stopped and not found_target then 
            if self:is_sliding() == false then

                if self:get_current_tile():is_edge() and self.slide_started then 
                    self:delete()
        
                end 
                
                local dest = self:get_tile(self:get_facing(), 1)
                local ref = self
                self:slide(dest, frames(12), frames(0), ActionOrder.Voluntary,
                    function()
                        ref.slide_started = true 
                    end
                )

                collision:slide(dest, frames(12), frames(0), ActionOrder.Voluntary,
                    function()
                        ref.slide_started = true 
                    end
                )
            end
        end

    end

    

    field:spawn(platter, tile)
    field:spawn(collision, tile)
    table.insert(self.platters, platter)
    platter.slot = #self.platters
end


function fumo_throw(self)
    if self.first_act then 
        anim:set_state("OPEN_PLATTER")
 
        anim:on_frame(1, function()
            self:toggle_counter(true)

        end)

        anim:on_frame(3, function()
            self:toggle_counter(false)

        end)

        local t = self:get_tile(self:get_facing(), 3)
        if t and not t:is_edge() then 
            anim:on_frame(3, function()
                if self.fumo and not self.fumo:is_deleted() then 
                    self.fumo:delete()
                end
                flying_fumo(self, t)

                Engine.play_audio(THROW_SFX, AudioPriority.Low)
            end)

            
            anim:on_complete(function()
                increment_pattern(self)
            
            end)

            self.first_act = false


        else
            -- If the throw is actually going to fail, just skip to next action
            increment_pattern(self)
        end
        
    end

end

function flying_fumo(self, tile)
    local offset_x = 30
    local offset_y = -48
    local flight_time = 54
    local current_time = flight_time
    local d_x = offset_x/flight_time
    local d_y = offset_y/flight_time
    local id = self:get_id()
    local damage = self.damage
    local ref = self

    local spell = graphic_init("spell", offset_x, offset_y, effect_texture, "effects.animation", -1, "FUMO_START", self, self:get_facing())
    local facing = 1
    if spell:get_facing() == Direction.Left then 
        facing = -1
    end
    spell.flying = false
    local shadow = graphic_init("spell", offset_x, offset_y, effect_texture, "effects.animation", 1, "FUMO_SHADOW", self, self:get_facing())

    spell.on_spawn_func = function(self)
        self:jump(tile, 100, frames(flight_time), frames(0), ActionOrder.Involuntary, function()
            self.flying = true
        end)

        shadow:slide(tile, frames(flight_time), frames(0), ActionOrder.Involuntary, function()
        end)

    end

    spell.update_func = function(self)
        if self.flying then 
            current_time = current_time - 1
            local off_x = facing * offset_x * current_time/flight_time
            self:set_offset(off_x, offset_y * current_time/flight_time)
            shadow:set_offset(off_x, 0)
            if current_time == 0 then 
                create_ground_fumo(self, ref, tile, id, damage)

                self:hide()
                shadow:hide()
                self:delete()
                shadow:delete()
            end
        end
    end

    spell.can_move_to_func = function()
        return true
    end

    shadow.can_move_to_func = function()
        return true
    end

    self:get_field():spawn(spell, self:get_tile())
    self:get_field():spawn(shadow, self:get_tile())

end

-- self here is the other fumo, not Yumeko. Yumeko is owner
function create_ground_fumo(self, owner, tile, id, damage)
    local fumo = graphic_init("obstacle", 0, 0, effect_texture, "effects.animation", -1, "FUMO_LAND", self, self:get_facing())
    owner.fumo = fumo
    local ref = self
    local from_yumeko = false
    fumo:set_health(1)
    fumo:toggle_hitbox(true)
    fumo:set_team(Team.Other)
    local field = owner:get_field()
    local collision = nil
    fumo.delete_func = function(self)
        if self:get_health() <= 0 then
            if not from_yumeko then 
                call_sprite(owner, {fumo:get_current_tile()}, 1)
            end
        end
    end

    fumo.can_move_to_func = function(tile)
        if tile:is_edge() or not tile:is_walkable() then
            return false
        end

        if (tile:get_team() == owner:get_team()) then
            return false
        end

        return not check_obstacles(tile, fumo) --and not check_characters(tile, self)
    end

    local defense = Battle.DefenseRule.new(224,DefenseOrder.Always)

    defense.can_block_func = function(judge, attacker, defender)
        local attacker_hit_props = attacker:copy_hit_props()
        if attacker:get_id() == collision:get_id() then 
            judge:block_damage()
            judge:block_impact()
            return 
        end

        if attacker_hit_props.damage > 0 then
            from_yumeko = (attacker and not attacker:is_deleted() and attacker:get_team() == owner:get_team())
          --  Engine.play_audio(GUARD_SFX, AudioPriority.Low)
            if from_yumeko then 
                local spell = Battle.Spell.new(owner:get_team())
                spell:set_hit_props(attacker_hit_props)
                local tiles = owner:get_field():find_tiles(function(t)
                    return not t:is_edge() and t:get_team() ~= owner:get_team()
                end)
                spell.update_func = function()
                    for i=1, #tiles
                    do
                        tiles[i]:attack_entities(spell)
                    end

                    spell:delete()
                end

                spell.attack_func = function()
                    Engine.play_audio(HIT_SFX, AudioPriority.Low)
                end

                owner:get_field():spawn(spell, attacker:get_current_tile())
            end
        end


    end
    
    fumo:add_defense_rule(defense)

    local function create_collision(tile)
        collision = Battle.Spell.new(owner:get_team())
        local hit_props = HitProps.new(
            damage,
            Hit.Impact | Hit.Flinch | Hit.Flash,
            Element.None, 
            id, 
            Drag.None
        )

        collision:set_hit_props(hit_props)

        collision.on_spawn_func = function()
            tile:attack_entities(collision)
            collision:delete()
        end

        collision.attack_func = function()
            Engine.play_audio(HIT_SFX, AudioPriority.Low)
        end

        field:spawn(collision, tile)
    end

    local counter = 0
    local delay = 180
    local speed = 60
    fumo.update_func = function(self)
        counter = counter + 1
        create_collision(self:get_current_tile())

        if counter == delay and owner:get_health() <= owner.max_health/2 then 
            self:get_animation():set_state("FUMO_RIDING_A_ROOMBA")
            self:get_animation():on_complete(function()
                self.moving = true
                self:share_tile(true)
            end)

            self:get_animation():refresh(self:sprite())
        end

        if self.moving then 
            if not self:is_moving() then 
                local dirs = {
                    Direction.Up,
                    Direction.Right,
                    Direction.Down,
                    Direction.Left
                }

                local valid = false
                local tile = nil
                while not valid and #dirs > 0
                do
                    local r = math.random(1, #dirs)
                    tile = self:get_tile(dirs[r], 1)
                    if self.can_move_to_func(tile) then 
                        valid = true
                        local dir = dirs[r]
                        if dir == Direction.Up or dir == Direction.Down then 
                            speed = 55
                        end
                    else
                        table.remove(dirs, r)
                    end
                end

                if valid then 
                    self:slide(tile, frames(speed), frames(0), ActionOrder.Voluntary, nil)
                    speed = 60
                end
            end
        end
    end

    field:spawn(fumo, self:get_current_tile())
end

function gunsword_pa(self)
    if self.first_act then 
        anim:set_state("GUNSWORD_PA")
        local point = nil
        local origin = nil
        local buster_endpoint = nil
        local offset = nil

        local height = nil

        anim:on_frame(1, function()
            self:toggle_counter(true)

            local facing = self:get_facing()
            local tile = self:get_tile(facing, 2)
            if not tile then return end

            self.tiles_to_highlight = {
                tile,
                tile:get_tile(Direction.reverse(facing), 1),
                tile:get_tile(Direction.Up, 1),
                tile:get_tile(Direction.Down, 1)
            }

            highlight_tiles(self, self.tiles_to_highlight, 18)
        end)


        anim:on_frame(5, function()
            self:toggle_counter(false)

            point = self:get_animation():point("buster")
            buster_endpoint = self:get_animation():point("endpoint")
            origin = self:get_animation():point("origin")

            offset = {
                y = -((origin.y - point.y + (buster_endpoint.y+10)/2)*2+5*2),
                x = (point.x/2 - origin.x)
            }   
    
            height = ((origin.y - point.y + buster_endpoint.y/2)*2+5*2)
            local facing_offset = 1
            local face = self:get_facing()
            if face == Direction.Left then 
                facing_offset = facing_offset * -1
            end

            offset.x = offset.x * facing_offset
            create_bullet_shell(nil, self, offset, offset.x*facing_offset, offset.y, height, face, 20 + math.random(0, 20), 2, 0, self:get_current_tile())

            create_shotgun_controller(self, self.shotgun_damage, self:get_tile(self:get_facing(), 1))

        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end

end
                
function create_shotgun_controller(user, damage, tile)
    local team = user:get_team()
    local id = user:get_id()
    local facing = user:get_facing()
    local field = user:get_field()
    local controller = Battle.Spell.new(team)
    controller:set_facing(facing)

    local tiles = {
        tile,
        tile:get_tile(facing, 1),
        tile:get_tile(Direction.join(facing, Direction.Up), 1),
        tile:get_tile(Direction.join(facing, Direction.Down), 1),

    }
    
    local function create_shot(tile)
        if not tile or tile:is_edge() then return end
        local spell = graphic_init("spell", math.random(-8, 8), math.random(-8, 8), shotgun_texture, "shotgun_effect.animation", -1, "DEFAULT", controller, facing, true)

        spell.attacking = true

        spell:set_hit_props(
            HitProps.new(
                damage, 
                Hit.Impact | Hit.Flinch, 
                Element.None,
                id,
                Drag.None
            )
        )

        spell:get_animation():on_frame(5, function()
            spell.attacking = false
        end)

        spell.update_func = function(self)
            if self.attacking then 
                self:get_current_tile():attack_entities(self)
            end
        end

        spell.attack_func = function()
            Engine.play_audio(HIT_SFX, AudioPriority.Low)
        end

        field:spawn(spell, tile)
    end


    local counter = 0
    local hits = 7
    controller.update_func = function(self)
        for i=1, #tiles
        do
            if counter == 0 or (counter+1) % 6 == 0 then 
                Engine.play_audio(EXPLOSION_SFX, AudioPriority.Low)
                create_shot(tiles[i])

            end

            tiles[i]:highlight(Highlight.Solid)
        end

        if (counter-1) == (5 * hits) then 
            self:delete() 
        end
        counter = counter + 1
    end


    field:spawn(controller, tile)
end

function create_bullet_shell(sound, user, start_pos, pX, pY, pZ, facing, time, count, spin, tile)
    local plusY = 0.0
    local speedY = 3*2 -- 2 is the scale

    local offset_facing = 1

    if facing == Direction.Left then 
        offset_facing = offset_facing * -1
    end

    local end_pos = {
        x = (start_pos.x - (8 + 8*2 * count)),
        y = start_pos.y + pZ
    }

   -- print("New shell. Start position is ", start_pos.x, start_pos.y)
    --print("and end is ", end_pos.x, end_pos.y, " and time ", time)


    local moveX = (start_pos.x - end_pos.x) / time * offset_facing
    local moveY = (start_pos.y - end_pos.y) / time
    local plusing = speedY / (time / 2) 

    local function update(pX, pY, time)

        local new_offset = {}
       -- print("moveX is ", moveX, "calculated using ", offset.x, " - (", start_pos.x, " - ", end_pos.x, ") / ", time)
    
        new_offset[1] = pX - moveX
      --  print("Changed x by ", moveX, "Also count is ", count)
        new_offset[2] = pY - moveY
        plusY = plusY + speedY
        speedY = speedY - plusing
    
        return new_offset
    end
    

    local spell = Battle.Spell.new(user:get_team())
    spell_anim = spell:get_animation()
    spell.time = time
     -- spell:set_offset(0.0, 0-(origin.y - point.y))
 --   if facing == Direction.Left then 
       -- start_pos.x = -start_pos.x
   -- end
    spell:set_offset(start_pos.x, start_pos.y)
 --   print("Spell initial offset is ", spell:get_offset().x, spell:get_offset().y)

    spell:sprite():set_layer(-3)


    spell:set_texture(Engine.load_texture(_folderpath.."shell.png"), true)
    spell:set_animation(_folderpath.."shell.animation")
    spell_anim:set_state("shell"..spin)
    spell_anim:refresh(spell:sprite())


    spell.update_func = function(self)
        self:get_animation():set_state("shell"..spin)
        self:get_animation():refresh(self:sprite())



        spin = spin+1
        if spin > 9 then 
            spin = 0 
        end

        self.time = self.time - 1
        if self.time == 0 then 
            if count > 0 then 
                create_bullet_shell(sound, user, self:get_offset(), pX, pY, 0, facing, math.floor(time/2), count-1, spin, self:get_current_tile())
            end

            self:hide()
            self:delete()
        end

        local new_offset = update(pX, pY, self.time)
        pX = new_offset[1]
        pY = new_offset[2]
        self:set_offset(pX, (pY - plusY))

    end


    user:get_field():spawn(spell, tile)

end

function choose_random_enemy_tile(self)
    local field = self:get_field()
    local tiles = field:find_tiles(function(tile)
        local c = tile:find_characters(function() return true end)
        return not tile:is_edge() and tile:get_team() ~= self:get_team() and #c == 0
    
    end)

    if #tiles == 0 then 
        tiles = field:find_tiles(function(tile)
            return not tile:is_edge() and tile:get_team() ~= self:get_team()
        
        end)
    end
    

    return tiles[math.random(1, #tiles)]
end

function choose_n_random_enemy_tiles(self, n)
    local field = self:get_field()
    local tile_set = {}
    local tiles = field:find_tiles(function(tile)
        return not tile:is_edge() and tile:get_team() ~= self:get_team()
    
    end)

    for i=1, n
    do
        if #tiles == 0 then break end
        local r = math.random(1, #tiles)
        table.insert(tile_set, tiles[r])
        table.remove(tiles, r)
    end    

    return tile_set
end

function choose_n_random_enemy_tiles_no_objects(self, n)
    local field = self:get_field()
    local tile_set = {}
    local team = self:get_team()
    local tiles = field:find_tiles(function(tile)
        return not tile:is_edge() and tile:get_team() ~= self:get_team() and not check_obstacles(tile, self)
    
    end)

    for i=1, n
    do
        if #tiles == 0 then break end
        local r = math.random(1, #tiles)
        table.insert(tile_set, tiles[r])
        table.remove(tiles, r)
    end    

    return tile_set
end

function choose_move(self, field)
    local team = self:get_team()

    local tiles = field:find_tiles(function(tile)
        return tile ~= self:get_current_tile() and self.can_move_to_func(tile)
    
    end)


    --print("Found ", #tiles, " possible tiles")

    if #tiles == 0 then 
        return self:get_current_tile()
    end

    return tiles[math.random(1, #tiles)]
end

function move_to_column_in_front_of_target(self)
    if self.first_act then 
        anim:set_state("MOVE")
        local could_move = false
        self.moving_to_enemy_tile = true
        local tile = choose_column_in_front_of_enemy(self, self:get_field())

        anim:on_frame(2, function()
            if tile and self.can_move_to_func(tile) then 
               could_move = true
            else
                --print("Can't do it now")
                tile = self:get_current_tile()
            end
            self:teleport(tile, ActionOrder.Voluntary, function()
                --print("Move")
            end)
        end)

        anim:on_complete(function()
            increment_pattern(self)
            if not could_move then 
                while self.in_sub_pattern
                do
                    increment_pattern(self)
                end
            end
        end)

        self.first_act = false
    end
end

function choose_column_in_front_of_enemy(self, field)
    local team = self:get_team()
    local tile

    local target = field:find_characters(function(c)
        return c:get_team() ~= team
    end)

    if not target[1] then 
       -- print("No targets")
        return nil
    end

    local offset = 1
    if self:get_facing() == Direction.Right then 
        offset = -1
    end

    -- X of tile in front of target
    local x = target[1]:get_current_tile():x() + offset
    for i = 1, field:height()
    do
        local t = field:tile_at(x, i)
        local check = self.can_move_to_func(t)
        if check then 
            return t
        end
    end


    return nil
end

function choose_move_line_up(self, field)
    local team = self:get_team()

    local target = field:find_characters(function(c)
        return c:get_team() ~= team
    end)

    if not target[1] then 
       -- print("No targets")
        return choose_move(self, field)
    end

    t_x = target[1]:get_current_tile():x()
    t_y = target[1]:get_current_tile():y()

    local facing = -1
    if self:get_facing() == Direction.Right then 
        facing = 1
    end

    local tiles = field:find_tiles(function(tile)
        return tile:y() == t_y and facing * (tile:x() - t_x) < 0 and self.can_move_to_func(tile)
    
    end)

   

    if #tiles == 0 then 
      --  print("No valid tiles")
        return choose_move(self, field)
    end


    return tiles[math.random(1, #tiles)]
end

function choose_enemy(self, field)
    local team = self:get_team()

    local target = field:find_characters(function(c)
        return c:get_team() ~= team
    end)

    if not target[1] then 
       -- print("No targets")
        return nil
    end

    t_x = target[1]:get_current_tile():x()
    t_y = target[1]:get_current_tile():y()

    local facing = -1
    if target[1]:get_facing() == Direction.Right then 
        facing = 1
    end

    local tile = field:tile_at(t_x+facing, t_y)

    return tile
end

function choose_ahead_of_enemy(self, field)
    local team = self:get_team()

    local target = field:find_characters(function(c)
        return c:get_team() ~= team
    end)

    if not target[1] then 
       -- print("No targets")
        return nil
    end

    t_x = target[1]:get_current_tile():x()
    t_y = target[1]:get_current_tile():y()

    local facing = -2
    if target[1]:get_facing() == Direction.Right then 
        facing = 2
    end

    local tile = field:tile_at(t_x+facing, t_y)

    return tile
end

function choose_move_back(self, field)
    local team = self:get_team()


    local x = 6
    if self:get_facing() == Direction.Right then 
        x = 1
    end

    local tiles = field:find_tiles(function(tile)
        return tile:x() == x and self.can_move_to_func(tile)
    
    end)

    if #tiles == 0 then 
      --  print("No valid tiles")
        return choose_move(self, field)
    end


    return tiles[math.random(1, #tiles)]
end

function choose_move_back_center(self, field)
    local team = self:get_team()

    local x = 6
    local y = 2

    
    if self:get_facing() == Direction.Right then 
        x = 1
    end

    local t = field:tile_at(x, y)

    if not self.can_move_to_func(t) then 
        return choose_move_back(self, field)
    end

    return t
end

function choose_near_front(self, field, allow_far, prefer_center, exclusive_front)
    local team = self:get_team()
    
    local x = field:width()

    local facing = self:get_facing()

    if facing == Direction.Right then 
        x = 1
    end

    local front_tiles = {
        field:tile_at(x, 1),
        field:tile_at(x, 2),
        field:tile_at(x, 3)
    }

    local function find_front_row()
        for i=1, #front_tiles
        do
            t = front_tiles[i]
            for j=1, field:width()
            do
                t = t:get_tile(facing, 1)
                if t and not t:is_edge() and t:get_team() == self:get_team() then 
                    front_tiles[i] = t
                else
                    break
                end
            end
        end
    end


    find_front_row()

    if prefer_center then 
        if self.can_move_to_func(front_tiles[2]) then 
            return front_tiles[2] 
        end
    end

    local tiles = nil 
    
    if allow_far then 
        tiles = {
            front_tiles[2],
            front_tiles[2]:get_tile(Direction.reverse(facing), 1),

            front_tiles[1],
            front_tiles[1]:get_tile(Direction.reverse(facing), 1),

            front_tiles[3],
            front_tiles[3]:get_tile(Direction.reverse(facing), 1)
        }
    else
        tiles = {
            front_tiles[2],

            front_tiles[1],

            front_tiles[3]
        }
    end

    local j = #tiles
    for i=1, #tiles
    do
        r = math.random(1, #tiles)
        if self.can_move_to_func(tiles[r]) then 
            return tiles[r]
        else
            table.remove(tiles, r)
        end
    end

    if exclusive_front then return nil end

    return choose_move(self, field)

end

function move_line_up(self)
    if self.first_act then 
        anim:set_state("MOVE")

        local tile = choose_move_line_up(self, self:get_field())

        anim:on_frame(3, function()
            if self.can_move_to_func(tile) then 
               -- print("Can reach")
            else
                --print("Can't do it now")
                tile = self:get_current_tile()
            end
            self:teleport(tile, ActionOrder.Voluntary, function()
               -- print("Move")
            end)
        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end
end

function move_to_back(self)
    if self.first_act then 
        anim:set_state("MOVE")

        local tile = choose_move_back(self, self:get_field())

        anim:on_frame(3, function()
            if self.can_move_to_func(tile) then 
              --  print("Can reach")
            else
                --print("Can't do it now")
                tile = self:get_current_tile()
            end
            self:teleport(tile, ActionOrder.Voluntary, function()
               -- print("Move")
            end)
        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end
end

function move_to_back_center(self)
    if self.first_act then 
        anim:set_state("MOVE")

        local tile = choose_move_back_center(self, self:get_field())

        anim:on_frame(3, function()
            if self.can_move_to_func(tile) then 
              --  print("Can reach")
            else
                --print("Can't do it now")
                tile = self:get_current_tile()
            end
            self:teleport(tile, ActionOrder.Voluntary, function()
                --print("Move")
            end)
        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end
end


function move_near_front(self)
    if self.first_act then 
        anim:set_state("MOVE")

        local tile = choose_near_front(self, self:get_field(), true)

        anim:on_frame(3, function()
            if self.can_move_to_func(tile) then 
           --     print("Can reach")
            else
             --   print("Can't do it now")
                tile = self:get_current_tile()
            end
            self:teleport(tile, ActionOrder.Voluntary, function()
               -- print("Move")
            end)
        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end
end

function move_to_front(self)
    if self.first_act then 
        anim:set_state("MOVE")

        local tile = choose_near_front(self, self:get_field())

        anim:on_frame(3, function()
            if self.can_move_to_func(tile) then 
           --     print("Can reach")
            else
             --   print("Can't do it now")
                tile = self:get_current_tile()
            end
            self:teleport(tile, ActionOrder.Voluntary, function()
               -- print("Move")
            end)
        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end
end

function move_to_front_center(self)
    if self.first_act then 
        anim:set_state("MOVE")

        local tile = choose_near_front(self, self:get_field(), false, true)

        anim:on_frame(3, function()
            if self.can_move_to_func(tile) then 
           --     print("Can reach")
            else
             --   print("Can't do it now")
                tile = self:get_current_tile()
            end
            self:teleport(tile, ActionOrder.Voluntary, function()
               -- print("Move")
            end)
        end)

        anim:on_complete(function()
            increment_pattern(self)
        
        end)

        self.first_act = false
    end
end

function move_to_enemy(self)
    if self.first_act then 
        anim:set_state("MOVE")
        local could_move = true
        anim:on_frame(3, function()
            local tile = choose_enemy(self, self:get_field())
            
            if not tile then 
                could_move = false
            else
                self.moving_to_enemy_tile = true
                if self.can_move_to_func(tile) then 
                  --  print("Target tile is ", tile:x(), tile:y())
                    self:teleport(tile, ActionOrder.Voluntary, function()

                    end)
                else
                    could_move = false
                end
            end
        end)

        anim:on_complete(function()
            self.moving_to_enemy_tile = false

            if could_move then 
                increment_pattern(self)
            else
                end_sub_pattern(self)
            end
        
        end)

        self.first_act = false
    end
end

function move_two_in_front_of_enemy(self)
    if self.first_act then 
        anim:set_state("MOVE")
        local could_move = true
        local could_move_in_front = true
        anim:on_frame(3, function()
            self.moving_to_enemy_tile = true
            local tile = choose_ahead_of_enemy(self, self:get_field())
            if not tile or not self.can_move_to_func(tile) then 
                self.moving_to_enemy_tile = false
                tile = choose_near_front(self, self:get_field(), false, false, true)
                could_move_in_front = false
                if not tile then could_move = false end
            end
            if could_move then
                if could_move_in_front then  
                    self.moving_to_enemy_tile = true
                end
                if self.can_move_to_func(tile) then 
                  --  print("Target tile is ", tile:x(), tile:y())
                    self:teleport(tile, ActionOrder.Voluntary, function()

                    end)
                else
                    could_move = false
                end
            end
        end)

        anim:on_complete(function()
            self.moving_to_enemy_tile = false

            if could_move then 
                increment_pattern(self)
            else
                end_sub_pattern(self)
            end
        
        end)

        self.first_act = false
    end
end


function increment_pattern(self)
   -- print("Pattern increment. Pattern was ", self.state.name)
    self.end_attack = false

    self.first_act = true
    self.state_done = false
    if self.should_clear_highlight then 
        self.tiles_to_highlight = {}
    end

    if self.defense then 
        self:remove_defense_rule(self.defense)
        self.defense = nil
        self.defense_guarded = false
        self.defense_should_reflect = false
        self.reflect_comp:eject()
    end

    if self.invincible then 
        self.invincible = false
        self:toggle_hitbox(true)
    end

    self.pattern_index = self.pattern_index + 1
    if self.pattern_index > #self.pattern then 
        self.pattern_index = 1
    end

    local next_state = self.pattern[self.pattern_index]
    self.state = next_state
   -- print("Moving to state named ", next_state.name)

    if next_state == self.states.start_sub_pattern then 
        self.in_sub_pattern = true
        increment_pattern(self)

    elseif next_state == self.states.finish_sub_pattern then 
        self.in_sub_pattern = false
        increment_pattern(self)

    elseif next_state == self.states.skip_if_above_x then 
        self.in_possible_skip = true

        if (self:get_health() / self.max_health)*100 > next_state.val then 
            skip_pattern(self)
        else 
            increment_pattern(self)
        end

    elseif next_state == self.states.finish_skip then 
        self.in_possible_skip = false
        increment_pattern(self)

    elseif next_state == self.states.start_optional then 
        local r = math.random(1, 4)
        self.in_optional = true 

        if r == 1 then
            increment_pattern(self)
        else
            skip_optional(self)
        end

    elseif next_state == self.states.finish_optional then 
        self.in_optional = false
        increment_pattern(self)
    end


   -- print("Changing to "..self.pattern_index..", which is "..self.pattern[self.pattern_index].name)

end

function check_obstacles(tile, self)
    local ob = tile:find_obstacles(function(o)
        return o:get_health() > 0 and o:get_id() ~= self:get_id()
    end)

    return #ob > 0 
end


function check_characters(tile, self)
    local characters = tile:find_characters(function(c)
        return c:get_id() ~= self:get_id() and c:get_team() ~= self:get_team()
    end)

    return #characters > 0

end

function create_wings(self)
    self:get_field():spawn(graphic_init("spell", 0, 0, texture, "yumeko.animation", 1, "WINGS", self, self:get_facing(), true), self:get_current_tile())
end

-- Parent to attach to, list to add to, anim state, offset, and optional tile this offset lands it on
function attach_node(parent, list, state, distance_x, distance_y, should_play_sound, optional_tile, optional_list)
    local n = parent:create_node()
    n:set_texture(effect_texture)
    n:set_layer(100)
    local anim = Engine.Animation.new(_folderpath.."effects.animation")
   -- anim:load(path)
    anim:set_state(state)
    n:set_offset(distance_x, distance_y)

    -- Last bool indicates completed something, for reference on if I should do something
    -- For the red tiles, it's finished animating
    -- Blades will have extra bool that tells me if I should search or I should animate
    local t = nil
    if not optional_tile then 
        t = {n, anim, false}
    else
        t = {n, anim, false, true, optional_tile}
        table.insert(optional_list, t)
    end

    table.insert(list, t)

    
    if should_play_sound then 
        anim:on_frame(4, function()
            Engine.play_audio(APPEAR_SFX, AudioPriority.Low)

        end)
    end
    anim:on_complete(function()
        t[3] = true
    end)

    anim:refresh(n)
end

function reinit_blade(self, node_data)
    if #self.pandaemonium_parents == 0 then return end
    local anim = node_data[2]
    node_data[3] = false
    anim:set_state("BLADE_SPAWN")
    anim:on_complete(function()
        node_data[3] = true
        node_data[4] = true
    end)

    anim:refresh(node_data[1])
end

-- The blades are going to be nodes on the parent. The entry added will include the node, anim, tile, and if it can search
-- The parent will loop over them every frame to see if a blade that can search has a Character (#find_characters() > 0)
-- For each that has a Character: blade made inactive, swaps animation state, on complete makes actual blade fall
-- Blade falls, on complete will re-initialize blade node
function create_falling_blade(self, tile, node_data)
    local spell = graphic_init("spell", 0, 0, effect_texture, "effects.animation", -1, "BLADE_FALL", self, self:get_facing(), true)
    local anim = spell:get_animation()
    spell:highlight_tile(Highlight.Solid)

    local hit_props = HitProps.new(
        self.damage,
        Hit.Impact | Hit.Flinch | Hit.Flash,
        Element.Sword, 
        self:get_id(), 
        Drag.None
    )

    spell:set_hit_props(hit_props)

    spell.attack_func = function()
        Engine.play_audio(HIT_SFX, AudioPriority.Low)
    end

    anim:on_frame(4, function()
        Engine.play_audio(KNIFE_SFX, AudioPriority.Low)
    end)
    anim:on_frame(14, function()
        spell:highlight_tile(Highlight.None)

    end)
    anim:on_frame(18, function()
        spell:get_current_tile():attack_entities(spell)
    end)

    anim:on_complete(function()
        reinit_blade(self, node_data)
        spell:delete()
    end)
    
    self:get_field():spawn(spell, tile)
end

function pull_blade(self, node_data)
    local anim = node_data[2]
    local team = self:get_team()
    local target = nil
    local list = self:get_field():find_characters(function(c)
        return c:get_team() ~= team
    end)

    if #list == 0 then 
   --     print("No characters in first go")
        return false
    end

    target = list[1]

    anim:set_state("BLADE_PULL")
    anim:on_frame(11, function()
        Engine.play_audio(LANCE_SFX, AudioPriority.Low)
    end)
    anim:on_complete(function()
        if not target or target:is_deleted() then 
         --   print("Character was not found after pulling out of ground")
            list = self:get_field():find_characters(function(c)
                return c:get_team() ~= team
            end)

            if #list == 0 then 
                reinit_blade(self, node_data)
                return
            else
                target = list[1]
            end
        end

        node_data[3] = true
        create_falling_blade(self, target:get_current_tile(), node_data)
    end)

    anim:refresh(node_data[1])

    node_data[3] = false
    return true
end

function pandaemonium_action(self)
    local action = Battle.CardAction.new(self, "FURY_OF_PANDAEMONIUM")
    local start = false
    local parent = nil
    local blade_parent1 = nil
    local blade_parent2 = nil
    local field = self:get_field()
    local dir = nil
    local cur_x = 0
    local cur_blade_x
    local created = 0

    local meta = action:copy_metadata()
    meta.time_freeze = true
    meta.skip_time_freeze_intro = false
    meta.shortname = "Fury of Pandaemonium"
    action:set_metadata(meta)

    action:add_anim_action(3, function()
        Engine.play_audio(DARK_SFX, AudioPriority.Low)
        create_wings(self)
    end)


    action:add_anim_action(5, function()
     --   Engine.play_audio(APOLLO_SFX, AudioPriority.Low)
        self.pandaemonium_check:eject()
        self.pandaemonium_active = true

    
    end)

    action:add_anim_action(7, function()
        self.pandaemonium_tiles = {}
        parent = Battle.Artifact.new()
        blade_parent1 = Battle.Artifact.new()
        blade_parent2 = Battle.Artifact.new()

        self.pandaemonium_parents = {parent, blade_parent1, blade_parent2}
        parent.nodes = {}
        parent.blades = {}
        dir = self:get_facing()
        if dir == Direction.Left then 
            cur_x = field:width()
            cur_blade_x = 1
        else
            cur_x = 1
            cur_blade_x = field:width()
        end

        local function all_char(c)
            return true
        end        
        
        -- Since it's an artifact, this runs in time freeze. When initial animating is done, 
        -- close update_func and rely on battlestep component to animate
        parent.update_func = function(_self, dt)
            local updated = false
            for i=1, #parent.nodes
            do
                if not parent.nodes[i][3] then 
                    parent.nodes[i][2]:update(dt, parent.nodes[i][1])
                    updated = true
                end
            end

            if not updated and #parent.nodes ~= 0 then 
                parent.update_func = function()
                end
            end
        end

        local c = Battle.Component.new(self, Lifetimes.Battlestep)
        -- Animate blades only, since panels are done by now
        c.update_func = function(_self, dt)
            if parent:is_deleted() then 
                c:eject()
                return 
            end

            for i=1, #parent.blades
            do
                local n = parent.blades[i]
                if not n[3] then -- If not done animating, animate
                    n[2]:update(dt, n[1])
                end

                if n[4] then -- If should search tiles, use tile in n[5] to search
                    local l = n[5]:find_characters(all_char)
                    -- Launch if any character found, but only attack enemies
                    if #l ~= 0 then 
                        -- pull_blade will look for a target, returns true if it found someone to attack
                        n[4] = not pull_blade(self, n)
                    end
                end
            end
        end

        self:register_component(c)

        parent:set_facing(Direction.Right)
        blade_parent1:set_facing(Direction.Left)
        blade_parent2:set_facing(Direction.Left)

        field:spawn(parent, field:tile_at(0, 0))
        field:spawn(blade_parent1, field:tile_at(field:width()+1, 1))
        field:spawn(blade_parent2, field:tile_at(field:width()+1, field:height()))

        start = true
    end)

    local counter = -1
    local tile = self:get_current_tile()
    local width = tile:width()/2
    local height = tile:height()/2
    tile = nil
    action.update_func = function()
        if start then 
            counter = counter + 1
            if created < 2*field:width() and counter % 6 == 0  then 
                -- Nodes are going to attach to parent.nodes so I don't have to worry about multiple loops
                -- All blades only go into parent as well, so I don't have to worry about multiple loops
                -- The blade_parents are just for layering, basically
                attach_node(parent, parent.nodes, "RED", cur_x * width, height-7, false)
                local tile = field:tile_at(cur_x, 1)
                attach_node(blade_parent1, parent.blades, "BLADE_SPAWN", cur_blade_x * width, 0, true, tile, parent.nodes)

                attach_node(parent, parent.nodes, "RED", cur_x * width, (field:height() * (height-7) + field:height()+1), false)
                tile = field:tile_at(cur_x, field:height())
                attach_node(blade_parent2, parent.blades, "BLADE_SPAWN", cur_blade_x * width, 0, false, tile, parent.nodes)

                if dir == Direction.Right then 
                    cur_x = cur_x + 1
                    cur_blade_x = cur_blade_x - 1
                else
                    cur_x = cur_x - 1
                    cur_blade_x = cur_blade_x + 1

                end

                created = created + 2

            end
        end
    end

    return action
end


function graphic_init(type, x, y, texture, animation, layer, state, user, facing, delete_on_complete, flip)
    flip = flip or false
    delete_on_complete = delete_on_complete or false
    facing = facing or nil
    
    local graphic = nil
    if type == "artifact" then 
        graphic = Battle.Artifact.new()

    elseif type == "spell" then 
        graphic = Battle.Spell.new(user:get_team())
    
    elseif type == "obstacle" then 
        graphic = Battle.Obstacle.new(user:get_team())

    end

    graphic:sprite():set_layer(layer)
    graphic:never_flip(flip)
    graphic:set_texture(texture, true)
    if facing then 
        graphic:set_facing(facing)
    end
    
    if user:get_facing() == Direction.Left then 
        x = x * -1
    end
    graphic:set_offset(x, y)
    local anim = graphic:get_animation()
    anim:load(_folderpath..animation)

    anim:set_state(state)
    anim:refresh(graphic:sprite())

    if delete_on_complete then 
        anim:on_complete(function()
            graphic:delete()
        end)
    end

    return graphic
end