require("stategraphs/commonstates")

--hiss_pre, vomit, swipe_pre
function TableConcat(t1,t2)
    for i=1,#t2 do
        t1[#t1+1] = t2[i]
    end
    return t1
end

local actionhandlers = 
{
    ActionHandler(ACTIONS.GOHOME, "gohome"),
    ActionHandler(ACTIONS.HAIRBALL, "hairball_hack"),
	ActionHandler(ACTIONS.EAT, "eat"),
	ActionHandler(ACTIONS.FEED, "pawgroundaction"),
	ActionHandler(ACTIONS.GIVE, "pawgroundaction"),
	ActionHandler(ACTIONS.GIVETOPLAYER, "pawgroundaction"),
	ActionHandler(ACTIONS.GIVEALLTOPLAYER, "pawgroundaction"),
    ActionHandler(ACTIONS.CATPLAYGROUND, "pawgroundaction"),
    ActionHandler(ACTIONS.CATPLAYAIR, "pounceplayaction"),
	ActionHandler(ACTIONS.ATTACK, "attack"),
	ActionHandler(ACTIONS.DEPLOY, "pawgroundaction"),
	ActionHandler(ACTIONS.PICKUP, "pawgroundaction"),
	ActionHandler(ACTIONS.HARVEST, "pawgroundaction"),
	ActionHandler(ACTIONS.PICK, "pawgroundaction"),
	ActionHandler(ACTIONS.DROP, "hairball"),
	ActionHandler(ACTIONS.SLEEPIN, "home"),
	ActionHandler(ACTIONS.REPAIR, "pawgroundaction"),
	ActionHandler(ACTIONS.MIGRATE, "pawgroundaction"),
	ActionHandler(ACTIONS.INVESTIGATE, "pawgroundaction"),
	ActionHandler(ACTIONS.COMBINESTACK, "pawgroundaction"),
	ActionHandler(ACTIONS.UNWRAP, "pawgroundaction"),
}

local mobCraftActions =
{
ActionHandler(ACTIONS.GOHOME, "gohome"),
	ActionHandler(ACTIONS.ATTACK, "attack"),
	ActionHandler(ACTIONS.PICKUP, "pawgroundaction"),
	ActionHandler(ACTIONS.FEED, "pawgroundaction"),
	ActionHandler(ACTIONS.INVESTIGATE, "pawgroundaction"),
	ActionHandler(ACTIONS.PICK, "pawgroundaction"),
	ActionHandler(ACTIONS.DROP, "hairball"),
	ActionHandler(ACTIONS.SLEEPIN, "home"),
	ActionHandler(ACTIONS.EAT, "eat"),
	ActionHandler(ACTIONS.HEAL, "pawgroundaction"),
	--ActionHandler(ACTIONS.FEED, "pawgroundaction"),
	ActionHandler(ACTIONS.FAN, "pawgroundaction"),
	ActionHandler(ACTIONS.DIG, "pawgroundaction"),
	ActionHandler(ACTIONS.ACTIVATE, "pawgroundaction"),	
	ActionHandler(ACTIONS.CHOP, "attack2"),
	ActionHandler(ACTIONS.MINE, "attack2"),
	ActionHandler(ACTIONS.COOK, "pawgroundaction"),
	ActionHandler(ACTIONS.FILL, "pawgroundaction"),
	ActionHandler(ACTIONS.DRY, "pawgroundaction"),
	ActionHandler(ACTIONS.ADDFUEL, "pawgroundaction"),
	ActionHandler(ACTIONS.ADDWETFUEL, "pawgroundaction"),
	ActionHandler(ACTIONS.LIGHT, "pawgroundaction"),
	ActionHandler(ACTIONS.BAIT, "pawgroundaction"),
	ActionHandler(ACTIONS.BUILD, "pawgroundaction"),
	ActionHandler(ACTIONS.PLANT, "pawgroundaction"),
	ActionHandler(ACTIONS.REPAIR, "pawgroundaction"),
	ActionHandler(ACTIONS.HARVEST, "pawgroundaction"),
	ActionHandler(ACTIONS.STORE, "pawgroundaction"),
	ActionHandler(ACTIONS.RUMMAGE, "pawgroundaction"),
	ActionHandler(ACTIONS.DEPLOY, "pawgroundaction"),
	ActionHandler(ACTIONS.HAMMER, "attack2"),
	ActionHandler(ACTIONS.FERTILIZE, "pawgroundaction"),
	ActionHandler(ACTIONS.MURDER, "pawgroundaction"),
	ActionHandler(ACTIONS.UNLOCK, "pawgroundaction"),
	ActionHandler(ACTIONS.TURNOFF, "pawgroundaction"),
	ActionHandler(ACTIONS.TURNON, "pawgroundaction"),
	ActionHandler(ACTIONS.SEW, "pawgroundaction"),
	ActionHandler(ACTIONS.COMBINESTACK, "pawgroundaction"),
	ActionHandler(ACTIONS.UPGRADE, "pawgroundaction"),
	ActionHandler(ACTIONS.WRITE, "pawgroundaction"),
	ActionHandler(ACTIONS.FEEDPLAYER, "pawgroundaction"),
	ActionHandler(ACTIONS.TERRAFORM, "pawgroundaction"),
	ActionHandler(ACTIONS.NET, "attack2"),
	ActionHandler(ACTIONS.CHECKTRAP, "pawgroundaction"),
	ActionHandler(ACTIONS.SHAVE, "pawgroundaction"),
	ActionHandler(ACTIONS.FISH, "pawgroundaction"),
	ActionHandler(ACTIONS.REEL, "pawgroundaction"),
	ActionHandler(ACTIONS.CATCH, "pawgroundaction"),
	ActionHandler(ACTIONS.TEACH, "pawgroundaction"),
	ActionHandler(ACTIONS.MANUALEXTINGUISH, "pawgroundaction"),
	ActionHandler(ACTIONS.RESETMINE, "pawgroundaction"),
	ActionHandler(ACTIONS.BLINK, "pawgroundaction"),
	ActionHandler(ACTIONS.GIVE, "pawgroundaction"),
	ActionHandler(ACTIONS.GIVETOPLAYER, "pawgroundaction"),
	ActionHandler(ACTIONS.GIVEALLTOPLAYER, "pawgroundaction"),
	--ActionHandler(ACTIONS.CHANGEIN, "changeskin"),
	ActionHandler(ACTIONS.SMOTHER, "pawgroundaction"),
	ActionHandler(ACTIONS.CASTSPELL, "pawgroundaction"),
	---PetActions--
	ActionHandler(ACTIONS.PET, "pawgroundaction"),
	ActionHandler(ACTIONS.ABANDON, "pawgroundaction"),
	ActionHandler(ACTIONS.WRAPBUNDLE, "pawgroundaction"),
	ActionHandler(ACTIONS.UNWRAP, "pawgroundaction"),
	ActionHandler(ACTIONS.UNLOCK, "pawgroundaction"),
	ActionHandler(ACTIONS.USEKLAUSSACKKEY, "pawgroundaction"),
}


local extraActions = 
{
	--ActionHandler(ACTIONS.SLEEPIN, "sleep"),
	ActionHandler(ACTIONS.TRAVEL, "taunt"),
	ActionHandler(ACTIONS.LOOKAT, "taunt"),
}

if MOBCRAFT == "Enable" then
	extraActions = mobCraftActions
end

actionhandlers = TableConcat(actionhandlers, extraActions)

local function SetSleeperAwakeState(inst)
    if inst.components.grue ~= nil then
        inst.components.grue:RemoveImmunity("sleeping")
    end
    if inst.components.talker ~= nil then
        inst.components.talker:StopIgnoringAll("sleeping")
    end
    if inst.components.firebug ~= nil then
        inst.components.firebug:Enable()
    end
    if inst.components.playercontroller ~= nil then
        inst.components.playercontroller:EnableMapControls(true)
        inst.components.playercontroller:Enable(true)
    end
    inst:OnWakeUp()
    inst.components.inventory:Show()
    inst:ShowActions(true)
end

local function SetSleeperSleepState(inst)
    if inst.components.grue ~= nil then
        inst.components.grue:AddImmunity("sleeping")
    end
    if inst.components.talker ~= nil then
        inst.components.talker:IgnoreAll("sleeping")
    end
    if inst.components.firebug ~= nil then
        inst.components.firebug:Disable()
    end
    if inst.components.playercontroller ~= nil then
        inst.components.playercontroller:EnableMapControls(false)
        inst.components.playercontroller:Enable(false)
    end
    inst:OnSleepIn()
    inst.components.inventory:Hide()
    inst:PushEvent("ms_closepopups")
    inst:ShowActions(false)
end

local events=
{
    CommonHandlers.OnSleep(),
    CommonHandlers.OnFreeze(),
    CommonHandlers.OnAttacked(true),
    CommonHandlers.OnDeath(),
    --CommonHandlers.OnLocomote(false,true),
	EventHandler("locomote", function(inst, data)
        if inst.sg:HasStateTag("busy") or inst.sg:HasStateTag("sleeping") and not inst.sg:HasStateTag("home") then
            return
        end
        local is_moving = inst.sg:HasStateTag("moving")
        local should_move = inst.components.locomotor:WantsToMoveForward()

        if inst.sg:HasStateTag("home") or inst.sg:HasStateTag("home_waking") then -- wakeup on locomote
            if inst.sleepingbag ~= nil and inst.sg:HasStateTag("sleeping") and inst.sg:HasStateTag("home") then
                inst.sleepingbag.components.sleepingbag:DoWakeUp()
                inst.sleepingbag = nil
				inst:Show()
				--inst.AnimState:PushAnimation("pig_pickup")
				SetSleeperAwakeState(inst)
				--inst.AnimState:PushAnimation("pig_pickup")
				inst.sg:GoToState("wake")
            end 
			elseif is_moving and not should_move then
            inst.sg:GoToState("walk_stop")
        elseif not is_moving and should_move then
            inst.sg:GoToState("walk_start")
        elseif data.force_idle_state and not (is_moving or should_move or inst.sg:HasStateTag("idle")) then
            inst.sg:GoToState("idle")
        end
    end),
    EventHandler("doattack", function(inst, data) 
        if not inst.components.health:IsDead() and not inst.sg:HasStateTag("busy") then 
            if data.target:HasTag("cattoyairborne") then
                if data.target.sg and (data.target.sg:HasStateTag("landing") or data.target.sg:HasStateTag("landed")) then
                    inst.components.combat:SetTarget(nil)
                else
                    inst.sg:GoToState("pounceplay", data.target)
                end
            elseif data.target and data.target:IsValid() and inst:GetDistanceSqToInst(data.target) > TUNING.CATCOON_MELEE_RANGE*TUNING.CATCOON_MELEE_RANGE then
                inst.sg:GoToState("pounceattack", data.target) 
            else
                inst.sg:GoToState("attack", data.target) 
            end
        end 
    end),
	
	EventHandler("ms_opengift",
        function(inst)
            if not inst.sg:HasStateTag("busy") then
                inst.sg:GoToState("opengift")
            end
        end),
}

local states=
{
    State{
        name = "idle",
        tags = {"idle", "canrotate"},

        onenter = function(inst)
            inst.components.locomotor:StopMoving()
            inst.AnimState:PlayAnimation("idle_loop")
        end,

        timeline = 
        {
            TimeEvent(25*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/swipe_tail") end),
        },

        events = 
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
        },
    },
	
	State{
        name = "opengift",
        tags = { "busy", "pausepredict" },

        onenter = function(inst)
            inst.components.locomotor:Stop()
            inst.components.locomotor:Clear()
            inst:ClearBufferedAction()

            --if IsNearDanger(inst) then
                --inst.sg.statemem.isdanger = true
                --inst.sg:GoToState("idle")
                --if inst.components.talker ~= nil then
                   -- inst.components.talker:Say(GetString(inst, "ANNOUNCE_NODANGERGIFT"))
                --end
                --return
           -- end

            inst.SoundEmitter:PlaySound("dontstarve/common/player_receives_gift")
            inst.AnimState:PlayAnimation("idle_loop", true)
            --inst.AnimState:PushAnimation("taunt", true)
            -- NOTE: the previously used ripping paper anim is called "giift_loop"

            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
                inst.components.playercontroller:EnableMapControls(false)
                inst.components.playercontroller:Enable(false)
            end
            inst.components.inventory:Hide()
            inst:PushEvent("ms_closepopups")
            inst:ShowActions(false)
            inst:ShowGiftItemPopUp(true)

            if inst.components.giftreceiver ~= nil then
                inst.components.giftreceiver:OnStartOpenGift()
            end
        end,

        timeline =
        {
            -- Timing of the gift box opening animation on giftitempopup.lua
            TimeEvent(155 * FRAMES, function(inst)
               -- inst.AnimState:PlayAnimation("gift_open_pre")
                --inst.AnimState:PushAnimation("taunt", true)
            end),
        },

        events =
        {
            EventHandler("firedamage", function(inst)
                --inst.AnimState:PlayAnimation("taunt")
                inst.sg:GoToState("idle", true)
                if inst.components.talker ~= nil then
                    inst.components.talker:Say(GetString(inst, "ANNOUNCE_NODANGERGIFT"))
                end
            end),
            EventHandler("ms_doneopengift", function(inst, data)
                inst.sg:GoToState("idle", true)
            end),
        },

        onexit = function(inst)
            if inst.sg.statemem.isdanger then
                return
            elseif not inst.sg.statemem.isopeningwardrobe then
                if inst.components.playercontroller ~= nil then
                    inst.components.playercontroller:EnableMapControls(true)
                    inst.components.playercontroller:Enable(true)
                end
                inst.components.inventory:Show()
                inst:ShowActions(true)
            end
            inst:ShowGiftItemPopUp(false)
        end,
    },
	
	State{
        name = "home",
        tags = {"busy", "silentmorph", "invisible" },

        onenter = function(inst)
            inst.components.locomotor:Stop()

            local target = inst:GetBufferedAction().target
            --local siesta = HasTag("siestahut")
            local failreason =
               -- (siesta ~= TheWorld.state.isday and
                    --(siesta
                    --and (TheWorld:HasTag("cave") and "ANNOUNCE_NONIGHTSIESTA_CAVE" or "ANNOUNCE_NONIGHTSIESTA")
                    --or (TheWorld:HasTag("cave") and "ANNOUNCE_NODAYSLEEP_CAVE" or "ANNOUNCE_NODAYSLEEP"))
               -- )or
			   (target ~= nil and target.components.burnable ~= nil and
                    target.components.burnable:IsBurning() and
                    "ANNOUNCE_NOSLEEPONFIRE") 
                --or (IsNearDanger(inst) and "ANNOUNCE_NODANGERSLEEP")
                -- you can still sleep if your hunger will bottom out, but not absolutely
                or (inst.components.hunger.current < TUNING.CALORIES_MED and "ANNOUNCE_NOHUNGERSLEEP")
                or (inst.components.beaverness ~= nil and inst.components.beaverness:IsStarving() and "ANNOUNCE_NOHUNGERSLEEP")
                or nil

            if failreason ~= nil then
                inst:PushEvent("performaction", { action = inst.bufferedaction })
                inst:ClearBufferedAction()
                inst.sg:GoToState("idle")
                if inst.components.talker ~= nil then
                    inst.components.talker:Say(GetString(inst, failreason))
                end
                return
            end

            inst.AnimState:PlayAnimation("eat")
            inst.sg:SetTimeout(5 * FRAMES)

            SetSleeperSleepState(inst)
        end,

        ontimeout = function(inst)
            local bufferedaction = inst:GetBufferedAction()
            if bufferedaction == nil then
                inst.AnimState:PlayAnimation("taunt")
                inst.sg:GoToState("idle", true)
                return
            end
            local home = bufferedaction.target
            if home == nil or
                not home:HasTag("cathouse") or
                --home:HasTag("hassleeper") or
                --home:HasTag("siestahut") ~= TheWorld.state.isday or
                (home.components.burnable ~= nil and home.components.burnable:IsBurning()) then
                --Edge cases, don't bother with fail dialogue
                --Also, think I will let smoldering pass this one
                inst:PushEvent("performaction", { action = inst.bufferedaction })
                inst:ClearBufferedAction()
                --inst.AnimState:PlayAnimation("taunt")
                inst.sg:GoToState("idle", true)
            else
                inst:PerformBufferedAction()
                inst.components.health:SetInvincible(true)
                inst:Hide()
                if inst.Physics ~= nil then
                    inst.Physics:Teleport(inst.Transform:GetWorldPosition())
                end
                if inst.DynamicShadow ~= nil then
                    inst.DynamicShadow:Enable(false)
                end
                inst.sg:AddStateTag("sleeping")
				inst.sg:AddStateTag("home")
                inst.sg:RemoveStateTag("busy")
                if inst.components.playercontroller ~= nil then
                    inst.components.playercontroller:Enable(true)
                end
            end
        end,

        onexit = function(inst)
            inst.components.health:SetInvincible(false)
            inst:Show()
            if inst.DynamicShadow ~= nil then
                inst.DynamicShadow:Enable(true)
            end
            if inst.sleepingbag ~= nil then
                --Interrupted while we are "sleeping"
                inst.sleepingbag.components.sleepingbag:DoWakeUp(true)
                inst.sleepingbag = nil
                SetSleeperAwakeState(inst)
            elseif not inst.sg.statemem.iswaking then
                --Interrupted before we are "sleeping"
                SetSleeperAwakeState(inst)
            end
        end,
    },

    State{
        name = "walk_start",
        tags = {"moving", "canrotate"},

        onenter = function(inst) 
            inst.AnimState:PlayAnimation("walk_pre")
        end,

        events =
        {   
            EventHandler("animover", function(inst) inst.sg:GoToState("walk") end ),        
        },
    },
        
    State{            
        name = "walk",
        tags = {"moving", "canrotate"},
        
        onenter = function(inst) 
            inst.components.locomotor:WalkForward()
            inst.AnimState:PlayAnimation("walk_loop")
        end,
        events=
        {   
            EventHandler("animover", function(inst) inst.sg:GoToState("walk") end ),        
        },
        timeline=
        {
            TimeEvent(FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(8*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(15*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(23*FRAMES, function(inst) PlayFootstep(inst) end),
        },
    },      
    
    State{            
        name = "walk_stop",
        tags = {"canrotate"},
        
        onenter = function(inst) 
            inst.components.locomotor:StopMoving()
            inst.AnimState:PlayAnimation("walk_pst")			
        end,

        events=
        {   
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end ),        
        },
    },

    State{
        
        name = "gohome",
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PlayAnimation("taunt_pre")
            inst.AnimState:PushAnimation("taunt", false)
            inst.AnimState:PushAnimation("taunt_pst", false)
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            --TimeEvent(6*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hiss_pre") end),
            TimeEvent(19*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hiss") end)
        },

        events =
        {
            EventHandler("animqueueover", function(inst) 
                inst:PerformBufferedAction()
                inst.sg:GoToState("idle") 
            end),
        },
    }, 

	State{
		name = "taunt",
		tags = {"busy"},
		
		onenter = function(inst)
			inst.Physics:Stop()
			inst.AnimState:PlayAnimation("furball_pre_loop")
            inst.numretches = 1
		end,

		onexit = function(inst)

		end,

		timeline =
		{
			TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hairball_hack") end),
		},

		events =
		{
			EventHandler("animover", function(inst) 
                if math.random() <= .25 then
                    inst.sg:GoToState("hairball") 
                else
                    inst.sg:GoToState("hairball_hack_loop")
                end
            end),
		},
	},
	
	State{
        name = "ready",
        tags = {"busy"},
        
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PlayAnimation("furball_pre_loop")
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hairball_hack") end),
        },

        events =
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),		
             
        },
    },
	
	State{
		name = "eat",
		tags = {"busy"},
		
		onenter = function(inst)
			inst.Physics:Stop()
			inst.AnimState:PlayAnimation("idle_loop", false)
            inst.numretches = 1
			
		end,

		onexit = function(inst)

		end,

		timeline =
		{
			TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hairball_hack") end),
			TimeEvent(4*FRAMES, function(inst) inst:PerformBufferedAction() end),
		},

		events =
		{
			EventHandler("animover", function(inst) 
                if math.random() <= .25 then
                    inst.sg:GoToState("idle") 
                else
                    inst.sg:GoToState("idle")
                end
            end),
		},
	},

	State{
        name = "special_atk2",
        tags = {"busy"},
        
        onenter = function(inst)
            if inst.specialatk == false then
				inst.sg:GoToState("idle")
			else
				inst.sg:GoToState("hairball_start")
			end	
        end,
		
		onexit = function(inst)
            
        end,
        
        timeline=
        {
           
        },
        
        events=
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") 
			--inst:AddTag("nospecial") 
			end),
        },
        
    },
	
	State{
		name = "hairball_start",
		tags = {"busy"},
		
		onenter = function(inst)
			inst.Physics:Stop()
			inst.AnimState:PlayAnimation("furball_pre_loop")
            inst.numretches = 1
		end,

		onexit = function(inst)

		end,

		timeline =
		{
			TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hairball_hack") end),
		},

		events =
		{
			EventHandler("animover", function(inst) 
                if math.random() <= .25 then
                    inst.sg:GoToState("hairball2") 
                else
                    inst.sg:GoToState("hairball_hack_loop2")
                end
            end),
		},
	},
	
    State{
        name = "hairball_hack_loop",
        tags = {"busy"},
        
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PlayAnimation("furball_pre_loop")
            inst.numretches = inst.numretches + 1
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hairball_hack") end),
        },

        events =
        {
            EventHandler("animover", function(inst) 
                local neutralmax = inst.neutralGiftPrefabs and #inst.neutralGiftPrefabs or 7
                local friendmax = inst.friendGiftPrefabs and #inst.friendGiftPrefabs or 7
                local MAX_RETCHES = (inst.components.follower and inst.components.follower.leader) and friendmax or neutralmax
                local rand = math.random()
                --print("Retching:", inst.numretches, .8/inst.numretches, rand)
                if inst.numretches >= MAX_RETCHES or rand < (.8/inst.numretches) then
                    inst.sg:GoToState("hairball")
                else
                    inst.sg:GoToState("hairball_hack_loop")
                end
            end),
        },
    },
	
	State{
        name = "hairball_hack_loop2",
        tags = {"busy"},
        
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PlayAnimation("furball_pre_loop")
            inst.numretches = inst.numretches + 1
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hairball_hack") end),
        },

        events =
        {
            EventHandler("animover", function(inst) 
                local neutralmax = inst.neutralGiftPrefabs and #inst.neutralGiftPrefabs or 7
                local friendmax = inst.friendGiftPrefabs and #inst.friendGiftPrefabs or 7
                local MAX_RETCHES = (inst.components.follower and inst.components.follower.leader) and friendmax or neutralmax
                local rand = math.random()
                --print("Retching:", inst.numretches, .8/inst.numretches, rand)
                if inst.numretches >= MAX_RETCHES or rand < (.8/inst.numretches) then
                    inst.sg:GoToState("hairball2")
                else
                    inst.sg:GoToState("hairball_hack_loop2")
                end
            end),
        },
    },

    State{
        name = "hairball",
        tags = {"busy", "hairball"},
        
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PushAnimation("furball", false)
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(37*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hairball_vomit") end),
            TimeEvent(46*FRAMES, function(inst)
                
                inst:PerformBufferedAction()
            end),
            TimeEvent(118*FRAMES, function(inst)
                    inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pickup")
                
            end),
        },

        events =
        {
            EventHandler("animqueueover", function(inst) 
                inst.sg:GoToState("idle")
            end),
        },
    },
	
	State{
        name = "hairball2",
        tags = {"busy", "hairball"},
        
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PushAnimation("furball", false)
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(37*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hairball_vomit") end),
            TimeEvent(46*FRAMES, function(inst) 
                inst.vomit = SpawnPrefab(inst:PickRandomGift(inst.numretches))
				--print ("Picking gift!")
                if inst.vomit and inst.vomit.components.inventoryitem and inst.vomit.components.inventoryitem.ondropfn then
                    inst.vomit.components.inventoryitem.ondropfn(inst.vomit)
                end
                if inst.vomit then
                    local downvec = TheCamera:GetDownVec()
                    local face = math.atan2(downvec.z, downvec.x) * (180/math.pi)
                    local pos = inst:GetPosition()
                    local offset = downvec:Normalize()
                    inst.vomit.Transform:SetPosition(pos.x + offset.x, pos.y + offset.y, pos.z + offset.z)
                    inst.Transform:SetRotation(-face)
					inst.specialatk = false
					inst.userfunctions.StartTimer(inst, inst.charge_time + 240) 
                end
                inst.last_hairball_time = GetTime()
                inst:PerformBufferedAction()
            end),
            TimeEvent(118*FRAMES, function(inst)
                    inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pickup")
                
            end),
        },

        events =
        {
            EventHandler("animqueueover", function(inst) 
                inst.sg:GoToState("idle")
            end),
        },
    },

    State{
        name = "pawground",
        tags = {"busy"},
        
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PlayAnimation("action")
            inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pickup")
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(6*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(13*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(20*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(27*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(34*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(42*FRAMES, function(inst) PlayFootstep(inst) end),
        },

        events =
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
        },
    },

    State{
        name = "pawgroundaction",
        tags = {"busy"},
        
        onenter = function(inst, target)
            inst.Physics:Stop()
            inst.AnimState:PlayAnimation("action")
            if math.random() < .5 then inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pickup") end
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(6*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(13*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(20*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(22*FRAMES, function(inst) inst:PerformBufferedAction() end),
            TimeEvent(27*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(34*FRAMES, function(inst) PlayFootstep(inst) end),
            TimeEvent(42*FRAMES, function(inst) PlayFootstep(inst) end),
        },

        events =
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
        },
    },

    State{
        name = "pounceplayaction",
        tags = {"canrotate", "busy", "jumping"},
        
        onenter = function(inst, target)
            inst.target = target
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(false)
            inst.AnimState:PlayAnimation("jump_grab")
        end,

        onexit = function(inst)
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(true)
        end,
        
        timeline =
        {
            TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pounce_pre") end),
            TimeEvent(25*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pounce") end),
            TimeEvent(26*FRAMES, function(inst) inst.Physics:SetMotorVelOverride(7,0,0) end),
            TimeEvent(31*FRAMES, function(inst) inst:PerformBufferedAction() end),
            TimeEvent(39*FRAMES,
                function(inst)
                    inst.Physics:ClearMotorVelOverride()
                    inst.components.locomotor:Stop()
                end),
        },

        events = 
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
        },
    },
	
	State{
        name = "special_atk23", --renamed because this name is already in use!
        tags = {"canrotate", "busy", "jumping"},
        
        onenter = function(inst, target) --players can't target trees but maybe they can target butterflies or not.
            inst.target = target
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(false)
            inst.AnimState:PlayAnimation("jump_grab")
        end,

        onexit = function(inst)
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(true)
        end,
        
        timeline =
        {
            TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pounce_pre") end),
            TimeEvent(25*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pounce") end),
            TimeEvent(26*FRAMES, function(inst) inst.Physics:SetMotorVelOverride(7,0,0) end),
            TimeEvent(31*FRAMES, function(inst) inst:PerformBufferedAction() end),
            TimeEvent(39*FRAMES,
                function(inst)
                    inst.Physics:ClearMotorVelOverride()
                    inst.components.locomotor:Stop()
                end),
        },

        events = 
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
        },
    },

    State{
        name = "hiss",
        tags = {"busy"},
        
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PlayAnimation("taunt_pre")
            inst.AnimState:PushAnimation("taunt", false)
            inst.AnimState:PushAnimation("taunt_pst", false)
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(0*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hiss_pre") end),
            TimeEvent(19*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hiss") end)
        },

        events =
        {
            EventHandler("animqueueover", function(inst) inst.sg:GoToState("idle") end),
        },
    },
	
	 State{
        name = "special_atk1",
        tags = {"busy"},
        
        onenter = function(inst)
            inst.Physics:Stop()
            inst.AnimState:PlayAnimation("taunt_pre")
            inst.AnimState:PushAnimation("taunt", false)
            inst.AnimState:PushAnimation("taunt_pst", false)
        end,

        onexit = function(inst)

        end,

        timeline =
        {
            TimeEvent(0*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hiss_pre") end),
            TimeEvent(19*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/hiss") end)
        },

        events =
        {
            EventHandler("animqueueover", function(inst) inst.sg:GoToState("idle") end),
        },
    },
	
	State{
    name = "attack2",
    tags = {"attack", "busy"},
     
    onenter = function(inst, target)
        local buffaction = inst:GetBufferedAction()
        local target = buffaction ~= nil and buffaction.target or nil
        inst.components.combat:SetTarget(target)
        inst.components.combat:StartAttack()
        inst.components.locomotor:Stop()
        inst.Physics:Stop()
        inst.AnimState:PlayAnimation("atk")
        if target ~= nil then
            if target:IsValid() then
                inst:FacePoint(target:GetPosition())
                inst.sg.statemem.attacktarget = target
            end
        end
    end,
     
    onexit = function(inst)
        inst.components.combat:SetTarget(nil)
    end,
     
    timeline =
    {
	    TimeEvent(5*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/swipe") end),
        TimeEvent(9*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/swipe_whoosh") end),
        TimeEvent(16*FRAMES, function(inst) inst:PerformBufferedAction() end),
    },
     
    events =
    {
        EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
    },
},

    State{
        name = "no",
        tags = {"attack", "canrotate", "busy", "jumping"},
        
        onenter = function(inst, target)
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(false)
            inst.components.combat:StartAttack()
            inst.AnimState:PlayAnimation("jump_atk")
            inst.hiss = (target:HasTag("smallcreature") and math.random() <= .5)
        end,

        onexit = function(inst)
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(true)
        end,
        
        timeline =
        {
            TimeEvent(5*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/attack") end),
            TimeEvent(6*FRAMES, function(inst) 
                inst.Physics:SetMotorVelOverride(12,0,0) 
                -- When the catcoon jumps, check if the target is a bird. If so, roll a chance for the bird to fly away
                local isbird = inst.components.combat and inst.components.combat.target and inst.components.combat.target:HasTag("bird")
                if isbird and math.random() > TUNING.CATCOON_ATTACK_CONNECT_CHANCE then
                    inst.components.combat.target:PushEvent("threatnear")
                end
            end),
            TimeEvent(14*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/jump") end),
            TimeEvent(19*FRAMES, function(inst) inst:PerformBufferedAction() end),
            TimeEvent(20*FRAMES,
                function(inst)
                    inst.Physics:ClearMotorVelOverride()
                    inst.components.locomotor:Stop()
                end),
        },
        
        events=
        {
            EventHandler("animover", function(inst) 
                if inst.hiss then
                    inst.hiss = false
                    inst.sg:GoToState("hiss") 
                else
                    inst.sg:GoToState("idle")
                end
            end),
        },
    },
	
	State{
        name = "attack",
        tags = {"attack", "canrotate", "busy", "jumping"},
        
        onenter = function(inst, target)
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(false)
            inst.components.combat:StartAttack()
            inst.AnimState:PlayAnimation("jump_atk")
       
        end,

        onexit = function(inst)
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(true)
        end,
        
        timeline =
        {
            TimeEvent(5*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/attack") end),
            TimeEvent(6*FRAMES, function(inst) 
                inst.Physics:SetMotorVelOverride(12,0,0) 
                -- When the catcoon jumps, check if the target is a bird. If so, roll a chance for the bird to fly away
                local isbird = inst.components.combat and inst.components.combat.target and inst.components.combat.target:HasTag("bird")
                if isbird and math.random() > TUNING.CATCOON_ATTACK_CONNECT_CHANCE then
                    inst.components.combat.target:PushEvent("threatnear")
                end
            end),
            TimeEvent(14*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/jump") end),
            TimeEvent(19*FRAMES, function(inst) inst:PerformBufferedAction() end),
            TimeEvent(20*FRAMES,
                function(inst)
                    inst.Physics:ClearMotorVelOverride()
                    inst.components.locomotor:Stop()
                end),
        },
        
        events=
        {
            EventHandler("animover", function(inst) 
                
                    inst.sg:GoToState("idle")
                
            end),
        },
    },

    State{
        name = "ability",
        tags = {"canrotate", "busy", "jumping"},
        
        onenter = function(inst, target)
            inst.target = target
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(false)
            inst.components.combat:StartAttack()
            inst.AnimState:PlayAnimation("jump_grab")
        end,

        onexit = function(inst)
            inst.components.locomotor:Stop()
            inst.components.locomotor:EnableGroundSpeedMultiplier(true)
        end,
        
        timeline =
        {
            TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pounce_pre") end),
            TimeEvent(25*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pounce") end),
            TimeEvent(26*FRAMES, function(inst) inst.Physics:SetMotorVelOverride(7,0,0) end),
            TimeEvent(31*FRAMES, function(inst) 
                if inst.target and (inst.target.prefab == "balloon" or inst.target:HasTag("bird")) and math.random() < (TUNING.CATCOON_ATTACK_CONNECT_CHANCE * 2) then
                    inst.components.combat:DoAttack()
                    inst.hiss = true
                elseif inst.target and inst.target:IsValid() and math.random() <= (TUNING.CATCOON_ATTACK_CONNECT_CHANCE * 1.5) and inst:GetDistanceSqToInst(inst.target) <= 3 then
                    inst.components.combat:DoAttack()
                end
            end),
            TimeEvent(39*FRAMES,
                function(inst)
                    inst.Physics:ClearMotorVelOverride()
                    inst.components.locomotor:Stop()
                end),
        },
        
        events=
        {
            EventHandler("animover", function(inst) 
                inst.target = nil
                if inst.hiss then
                    inst.hiss = false
                    inst.sg:GoToState("hiss")
                else
                    inst.sg:GoToState("idle") 
                end
            end),
        },
    },
	
	    State{
        name = "hit",
        tags = {"busy"},
        
        onenter = function(inst)
            inst.AnimState:PlayAnimation("hit")
            inst.Physics:Stop()            
        end,
        
        events=
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end ),
        },        
    },   
	
	State {
        name = "sleep",
        tags = { "sleeping" }, --add tag "busy" if you hate sliding

        onenter = function(inst)
            if inst.components.locomotor ~= nil then
                inst.components.locomotor:StopMoving()
            end
            inst.AnimState:PlayAnimation("sleep_pre")
            --if fns ~= nil and fns.onsleep ~= nil then
                --fns.onsleep(inst)
            --end
        end,

        --timeline = timelines ~= nil and timelines.starttimeline or nil,
		
		timeline = 
		{
			TimeEvent(26*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/yawn") end)
		},

        events =
        {
            EventHandler("animqueueover", function(inst) inst.sg:GoToState("sleeping") end ),
            EventHandler("onwakeup", function(inst) inst.sg:GoToState("wake") end),
        },
    },

    State
    {
        name = "sleeping",
        tags = { "sleeping" },

        --onenter = onentersleeping,
		
		onenter = function(inst)
				inst.components.locomotor:StopMoving()
				local hungerpercent = inst.components.hunger:GetPercent()
				if hungerpercent ~= nil and hungerpercent ~= 0 then -- We don't want players to heal out starvation.
				inst.components.health:DoDelta(10, false)
				end
				inst.AnimState:PlayAnimation("sleep_loop")
			end,

        
		timeline = 
		{
			TimeEvent(37*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/sleep") end)
		},

        events =
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("sleeping") end ),
			EventHandler("onwakeup", function(inst) inst.sg:GoToState("wake") end),
        },
    },

    State
    {
        name = "wake",
        tags = { "busy", "waking" },

        onenter = function(inst)
            if inst.components.locomotor ~= nil then
                inst.components.locomotor:StopMoving()
            end
            inst.AnimState:PlayAnimation("sleep_pst")
            if inst.components.sleeper ~= nil and inst.components.sleeper:IsAsleep() then
                inst.components.sleeper:WakeUp()
            end
            --if fns ~= nil and fns.onwake ~= nil then
                --fns.onwake(inst)
            --end
        end,
		
		timeline =
		{
			TimeEvent(31*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/pickup") end)
		},
		
        events =
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
        },
    },
	
	

    State{
        name = "death",
        tags = {"busy"},

        onenter = function(inst)
            inst.AnimState:PlayAnimation("death")
			
            inst.Physics:Stop()
            RemovePhysicsColliders(inst)
            inst.components.lootdropper:DropLoot(inst:GetPosition())
			inst.components.inventory:DropEverything(true)
			
            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
            end
        end,
		
		timeline = 
		{
			TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/catcoon/death") end),
		},
		
        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    if MOBGHOST == "Enable" then
                     inst:PushEvent(inst.ghostenabled and "makeplayerghost" or "playerdied", { skeleton = false })
					else
						TheWorld:PushEvent("ms_playerdespawnanddelete", inst)
					end
                end
            end),
        },
    },

}

CommonStates.AddFrozenStates(states)
  
return StateGraph("catplayer", states, events, "idle", actionhandlers)
