Jump to content

[Solved] Attempt to call method 'SetUnderPhysics' (a nil value)


Recommended Posts

Hello,

I'm currently running into the following error when attempting to switch my characters build, bank, and stategraph then attempt to move.

[00:01:28]: [string "../mods/Whisper/scripts/stategraphs/SGmolep..."]:551: attempt to call method 'SetUnderPhysics' (a nil value)
LUA ERROR stack traceback:
    ../mods/Whisper/scripts/stategraphs/SGmolep.lua:551 in (field) onenter (Lua) <550-557>
    scripts/stategraph.lua:501 in (method) GoToState (Lua) <450-511>
    ../mods/Whisper/scripts/stategraphs/SGmolep.lua:185 in (field) fn (Lua) <161-189>
    scripts/stategraph.lua:400 in (method) HandleEvents (Lua) <391-411>
    scripts/stategraph.lua:145 in (method) Update (Lua) <109-148>
    scripts/update.lua:218 in () ? (Lua) <149-228>

The calls are being made from stategraph SGmolep.lua when I attempt to move after changing. The stategraph should be pulling from my characters prefab, but for whatever reason it's not, this only occurs when caves are on.

local function SetUnderPhysics(inst)
    if inst.isunder ~= true then
        inst.isunder = true
        inst.Physics:SetCollisionGroup(COLLISION.CHARACTERS)
        inst.Physics:ClearCollisionMask()
        inst.Physics:CollidesWith(COLLISION.WORLD)
        inst.Physics:CollidesWith(COLLISION.OBSTACLES)
    end
end

 

If anyone could help with this it would be appreciated or if you know an easier way to swap my characters build with a mole and only perform it's walk animation it would be appreciated.

 

Thank you,

Red

Edited by RedHairedHero
Link to comment
Share on other sites

Spoiler

local MakePlayerCharacter = require "prefabs/player_common"

local assets = {
    Asset("SCRIPT", "scripts/prefabs/player_common.lua"),
    Asset( "ANIM", "anim/whisper.zip" ),
    Asset( "ANIM", "anim/whisper_furry.zip" ),
    Asset( "ANIM", "anim/whisper_dark.zip" ),
    Asset( "ANIM", "anim/whisper_dark_furry.zip" ),
    Asset("ANIM", "anim/mole_build.zip"),
    Asset("ANIM", "anim/mole_basic.zip"),
    Asset("ANIM", "anim/mole_move_fx.zip"),
    Asset("ANIM", "anim/player_jump.zip"),
    Asset("SOUND", "sound/mole.fsb"),
}

local prefabs = 
{
    "mole_move_fx",
    "molep"
}

-- Custom starting items
local start_inv = 
{
    "carrot",
    "carrot",
    "carrot",
    "carrot",
}

-- When the character is revived from human
local function onbecamehuman(inst)
    -- Set speed when reviving from ghost (optional)
    inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 + .25)
end

local function onbecameghost(inst)
    -- Remove speed modifier when becoming a ghost
   inst.CanExamine = nil
   inst:RemoveTag("underground")
   inst:AddTag("aboveground")
   inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "whisper_speed_mod")
end

-- When loading or spawning the character
local function onload(inst)
    inst:ListenForEvent("ms_respawnedfromghost", onbecamehuman)
    inst:ListenForEvent("ms_becameghost", onbecameghost)

    if inst:HasTag("playerghost") then
        onbecameghost(inst)
    else
        onbecamehuman(inst)
    end
end

local function oneat(inst, food)
    
    local extrahunger = 0
    
    if food.prefab == "carrot" or food.prefab == "carrot_cooked" then
        if food.components.perishable:IsStale() then
            extrahunger = 4
        else if food.components.perishable:IsSpoiled() then
            extrahunger = 2
        else --if it's fresh
            extrahunger = 6
        end
        end
    end
    
    if food and food.components.edible and food.prefab == "carrot"  then
        inst.components.hunger:DoDelta(extrahunger);
    end
    
    if food and food.components.edible and food.prefab == "carrot_cooked"  then
        inst.components.hunger:DoDelta(extrahunger);
    end
    
end

local function OnResetBeard(inst)
    inst:RemoveTag("FURRY")
    if inst:HasTag("DARK") then
        inst.AnimState:SetBuild("whisper_dark")
    else
        inst.AnimState:SetBuild("whisper")
    end
end 
    
local function OnGrowBeard(inst)    
    inst:AddTag("FURRY")
    if inst:HasTag("DARK") then
        inst.AnimState:SetBuild("whisper_dark_furry")
    else
        inst.AnimState:SetBuild("whisper_furry")
    end
    inst.components.beard.bits = 4
end
    
local function CannotExamine(inst)
    return false
end

local function SetUnderPhysics(inst)
    if inst.isunder ~= true then
        inst.isunder = true
        inst.Physics:SetCollisionGroup(COLLISION.CHARACTERS)
        inst.Physics:ClearCollisionMask()
        inst.Physics:CollidesWith(COLLISION.WORLD)
        inst.Physics:CollidesWith(COLLISION.OBSTACLES)
    end
end

local function SetAbovePhysics(inst)
    if inst.isunder ~= false then
        inst.isunder = false
        ChangeToCharacterPhysics(inst)
    end
end

local function burrow (inst)
    if inst:HasTag("aboveground") then
        inst:RemoveTag("aboveground")
        inst:AddTag("underground")
        inst.AnimState:SetBank("mole")
        inst.AnimState:SetBuild("mole_build")
        inst:SetStateGraph("SGmolep")
        inst.CanExamine = CannotExamine
        inst.components.locomotor.fasteronroad = false
        --print("underground")
    else if inst:HasTag("underground") then
        inst:RemoveTag("underground")
        inst:AddTag("aboveground")
        inst.CanExamine = nil
        inst.components.locomotor.fasteronroad = true
        --print("aboveground")
    end
    end
end

local function transform (inst)
    inst.AnimState:OverrideSymbol("shadow_hands", "shadow_hands", "shadow_hands")
    if not inst:HasTag("DARK") and not inst:HasTag("notransform") and not inst:HasTag("underground") then--if the user is normal, not underground, and has enough sanity, change to dark
        inst:AddTag("DARK")
        inst.components.beard.prize = "beardhair"
    else if inst:HasTag("DARK") then--if user is dark, change to normal
        inst:RemoveTag("DARK")
        inst.components.beard.prize = "manrabbit_tail"
    end
    end

    if inst:HasTag("FURRY") and inst:HasTag("DARK") then
        inst.AnimState:SetBuild("whisper_dark_furry")
    else if not inst:HasTag("FURRY") and inst:HasTag("DARK") then
        inst.AnimState:SetBuild("whisper_dark")
    else if inst:HasTag("FURRY") and not inst:HasTag("DARK") then
        inst.AnimState:SetBuild("whisper_furry")
    else
        inst.AnimState:SetBuild("whisper")
    end
    end
    end
    
    if inst:HasTag("DARK") then
        inst.components.health:SetAbsorptionAmount(0.25)
        inst.components.combat.damagemultiplier = 1.25
        inst.components.sanity.dapperness = (TUNING.CRAZINESS_SMALL)--SMALL Night Armour, MED is Dark Sword
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 )
        inst.soundsname = "wickerbottom"
    else
        inst.components.health:SetAbsorptionAmount(-0.25)
        inst.components.combat.damagemultiplier = 0.75 
        inst.components.sanity.dapperness = 0
        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 + .25)
        inst.soundsname = "willow"
     end
end

local function OnKeyPressed(inst, data)    
    if data.inst == ThePlayer then
        if data.key == KEY_B and not inst:HasTag("DARK") then
            if inst:HasTag("aboveground") then
                inst.AnimState:PlayAnimation("jump")
                inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/jump")
                inst:DoTaskInTime(inst.AnimState:GetCurrentAnimationLength(), burrow)
            else if inst:HasTag("underground") then
                inst:Hide()
                ChangeToCharacterPhysics(inst)
                inst.AnimState:SetBank("wilson")
                if inst:HasTag("FURRY") then
                    inst.AnimState:SetBuild("whisper_furry")
                else
                    inst.AnimState:SetBuild("whisper")
                end
                inst.SoundEmitter:KillSound("move")    
                inst:SetStateGraph("SGwilson")
                inst:Show()
                inst.AnimState:PlayAnimation("jumpout")
                inst.SoundEmitter:PlaySound("dontstarve/creatures/mandrake/plant_dirt")    
                burrow (inst)
            end
            end
            --print("KEY_B has been pressed.")
        end
        
        if data.key == KEY_T then
            inst.AnimState:OverrideSymbol("shadow_hands", "shadow_skinchangefx", "shadow_hands")
            inst.AnimState:OverrideSymbol("shadow_ball", "shadow_skinchangefx", "shadow_ball")
            inst.AnimState:OverrideSymbol("splode", "shadow_skinchangefx", "splode")
            inst.AnimState:PlayAnimation("skin_change")
            inst.SoundEmitter:PlaySound("dontstarve/creatures/werepig/transformToPig")
            inst:DoTaskInTime(inst.AnimState:GetCurrentAnimationLength(), transform)
            --print("KEY_T has been pressed.")            
        end
    end
end

local function controltransform (inst, data)

if not inst:HasTag("playerghost") then
    if inst.components.sanity.current <= 30 and inst:HasTag("DARK") then
        --If in dark mode and sanity falls below 30 force the player back into normal mode
        SendRPCToServer(RPC.DoWidgetButtonAction, ACTIONS.DARKSIDE.code, inst, ACTIONS.DARKSIDE.mod_name)
        inst:AddTag("notransform")
    else if inst.components.sanity.current <= 30 then
        --If the players sanity is below or equal to 30 prevent them from transforming into dark mode
        inst:AddTag("notransform")
    else if inst.components.sanity.current > 30 then
        --If sanity is above 30 allow transformation
        inst:RemoveTag("notransform")
    end
    end
    end
end

end

local function sanityfn(inst)
    local delta = 0
    if TheWorld.state.isday then
            delta = TUNING.SANITY_NIGHT_MID
    end
    return delta
end

-- This initializes for both the server and client. Tags can be added here.

local common_postinit = function(inst)
    inst:AddComponent("keyhandler")
    inst:ListenForEvent("keypressed", OnKeyPressed)
    inst.MiniMapEntity:SetIcon( "whisper.tex" )

    inst:AddTag("aboveground")
    inst:AddTag("bearded")
    
    inst.SetUnderPhysics = SetUnderPhysics
    inst.SetAbovePhysics = SetAbovePhysics
    
    MakeCharacterPhysics(inst, 99999, 0.5)
    inst.Physics:SetMass(99999)
    SetUnderPhysics(inst)
end

-- This initializes for the server only. Components are added here.
local master_postinit = function(inst)
    inst.soundsname = "willow"
    
    
    
    inst.components.sanity.custom_rate_fn = sanityfn
    inst.components.eater:SetDiet({ FOODTYPE.VEGGIE }, { FOODTYPE.VEGGIE })
    
    inst:AddComponent("beard")
    inst.components.beard.onreset = OnResetBeard
    inst.components.beard:AddCallback( 7 , OnGrowBeard )
    
    inst.components.sanity.night_drain_mult = 0
    
    inst:AddComponent("lootdropper")
    
    if inst:HasTag("DARK") then
        inst.components.beard.prize = "beardhair"
    else
        inst.components.beard.prize = "manrabbit_tail"
    end
    
    -- Listener for eating
    inst.components.eater:SetOnEatFn(oneat)
        
    -- Stats    
    inst.components.health:SetMaxHealth(200)
    inst.components.hunger:SetMax(150)
    inst.components.sanity:SetMax(150)
    
    -- Damage multiplier (optional)
    inst.components.health:SetAbsorptionAmount(-0.25)
    inst.components.combat.damagemultiplier = 0.75
    
    -- Hunger rate (optional)
    if inst:HasTag("aboveground") then
        inst.components.hunger.hungerrate = 1 * TUNING.WILSON_HUNGER_RATE 
    else if inst:HasTag("underground") then
        inst.components.hunger.hungerrate = 3 * TUNING.WILSON_HUNGER_RATE 
    end
    end
    
    inst:ListenForEvent("sanitydelta", controltransform)
    
    inst.OnLoad = onload
    inst.OnNewSpawn = onload

    return inst
end

return MakePlayerCharacter("whisper", prefabs, assets, common_postinit, master_postinit, start_inv)

Spoiler

require("stategraphs/commonstates")

local WALK_SPEED = 4
local RUN_SPEED = 7

--[[
local MOLE_PEEK_INTERVAL = 20
local MOLE_PEEK_VARIANCE = 5

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

local actionhandlers = 
{
    ActionHandler(ACTIONS.GOHOME, "action"),
    --ActionHandler(ACTIONS.ATTACK, "attack"),
    ActionHandler(ACTIONS.PICKUP, "steal_pre_under"),
    ActionHandler(ACTIONS.PICK, "steal_pre_under"),
    ActionHandler(ACTIONS.DROP, "action"),
    ActionHandler(ACTIONS.HARVEST, "action"),
    ActionHandler(ACTIONS.SLEEPIN, "home"),
    ActionHandler(ACTIONS.EAT, "action"),
    ActionHandler(ACTIONS.HEAL, "action"),
    ActionHandler(ACTIONS.GIVE, "action"),
    ActionHandler(ACTIONS.GIVETOPLAYER, "action"),
    ActionHandler(ACTIONS.GIVEALLTOPLAYER, "action"),
    ActionHandler(ACTIONS.JUMPIN, "action"),
    ActionHandler(ACTIONS.MIGRATE, "action"),
    ActionHandler(ACTIONS.COMBINESTACK, "action"),
    ActionHandler(ACTIONS.DEPLOY, "make_molehill"),
    
    --ActionHandler(ACTIONS.PICK, "eat"),
    ActionHandler(ACTIONS.ACTIVATE, "action"),    
}

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

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

if MOBCRAFTCU == "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)
    --inst.sg:GoToState("taunt")
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=
{
    --EventHandler("attacked", function(inst) if not inst.components.health:IsDead() and not inst.sg:HasStateTag("attack") then inst.sg:GoToState("hit") end end),
    --EventHandler("doattack", function(inst, data) inst.sg:GoToState("attack", data.target) end),
    EventHandler("death", function(inst) inst.sg:GoToState("death") end),
    --CommonHandlers.OnSleep(),
    --CommonHandlers.OnFreeze(),
    --CommonHandlers.OnLocomote(true,false),
    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("idle")
                inst.AnimState:PlayAnimation("idle_under")
                inst.sg:GoToState("idle", true)
            
                
            end 
            elseif is_moving and not should_move then
            inst.sg:GoToState("run_stop")
        elseif not is_moving and should_move then
            inst.sg:GoToState("run_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("ms_opengift",
        function(inst)
            if not inst.sg:HasStateTag("busy") then
                inst.sg:GoToState("opengift")
            end
        end),
--]]
}

local function SpawnMoveFx(inst)
    SpawnPrefab("mole_move_fx").Transform:SetPosition(inst.Transform:GetWorldPosition())
end

local function PlayStunnedSound(inst)
    if not inst.SoundEmitter:PlayingSound("stunned") then
        inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/sleep", "stunned")
    end
end

local function KillStunnedSound(inst)
    inst.SoundEmitter:KillSound("stunned")
end

local states=
{
    
    
    State{
        name = "death",
        tags = {"busy"},
        
        onenter = function(inst)
            inst.SoundEmitter:KillSound("move")
            inst.SoundEmitter:KillSound("sniff")
            --inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/death")
            inst.AnimState:PlayAnimation("death")
            inst.Physics:Stop()
            RemovePhysicsColliders(inst)            
            --inst.components.lootdropper:DropLoot(Vector3(inst.Transform:GetWorldPosition()))
            inst.components.inventory:DropEverything(true)
         
            if inst.components.playercontroller ~= nil then
               inst.components.playercontroller:RemotePausePrediction()
            end
        end,
        
        timeline = 
        {
            
        },
        
        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    if MOBGHOSTCU == "Enable" then
                        inst:PushEvent(inst.ghostenabled and "makeplayerghost" or "playerdied", { skeleton = false })
                    else
                        TheWorld:PushEvent("ms_playerdespawnanddelete", inst)
                    end
                    --inst:PushEvent(inst.ghostenabled and "makeplayerghost" or "playerdied", { skeleton = false })
                end
            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("taunt")
            --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 = "enter",
        tags = { "busy" },
        onenter = function(inst)
            inst.Physics:Stop()
            inst:SetAbovePhysics()
            inst.AnimState:PlayAnimation("enter")
            inst.SoundEmitter:KillSound("move")
        end,

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

    State
    {
        name = "peek",
        tags = { "busy" },
        onenter = function(inst)
            inst.Physics:Stop()
            inst:SetAbovePhysics()
            inst.SoundEmitter:KillSound("move")
            inst.AnimState:PlayAnimation("enter")

            inst.peek_interval = GetRandomWithVariance(MOLE_PEEK_INTERVAL, MOLE_PEEK_VARIANCE)
            inst.last_above_time = GetTime()
            inst:PerformBufferedAction()
        end,

        timeline =
        {
            TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/emerge") end),
            TimeEvent(3*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/emerge_voice") end),
        },

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

    State
    {
        name = "steal_pre_under",
        tags = { "busy" },
        onenter = function(inst, data)
            inst.Physics:Stop()
            inst:SetAbovePhysics()
            inst.SoundEmitter:KillSound("move")
            inst.AnimState:PlayAnimation("enter")
            inst.AnimState:PushAnimation("idle", false)
        end,

        timeline =
        {
            TimeEvent(1*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/emerge") end),
            TimeEvent(3*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/emerge_voice") end),
            TimeEvent(26*FRAMES, function(inst)
                if not inst.SoundEmitter:PlayingSound("sniff") then
                    inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/sniff", "sniff")
                end
            end),
            TimeEvent(77*FRAMES, function(inst) inst.SoundEmitter:KillSound("sniff") end),
        },

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

        onexit = function(inst)
            inst.SoundEmitter:KillSound("sniff")
        end,
    },

    State
    {
        name = "steal_pre_above",
        tags = { "busy" },
        onenter = function(inst, data)
            inst.Physics:Stop()
            inst:SetAbovePhysics()
            inst.SoundEmitter:KillSound("move")
            inst.AnimState:PlayAnimation("idle", false)
        end,

        timeline =
        {
            TimeEvent(1*FRAMES, function(inst)
                if not inst.SoundEmitter:PlayingSound("sniff") then
                    inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/sniff", "sniff")
                end
            end),
            TimeEvent(52*FRAMES, function(inst) inst.SoundEmitter:KillSound("sniff") end),
        },

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

        onexit = function(inst)
            inst.SoundEmitter:KillSound("sniff")
        end,
    },

    State
    {
        name = "steal",
        tags = { "busy", "canrotate" },
        onenter = function(inst, playanim)
            inst.Physics:Stop()
            inst:SetAbovePhysics()
            inst.AnimState:PlayAnimation("action")
            inst.AnimState:PushAnimation("idle", false)
        end,

        timeline =
        {
            TimeEvent(9*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/pickup") end),
            TimeEvent(12*FRAMES, function(inst) inst:PerformBufferedAction() end),
            TimeEvent(27*FRAMES, function(inst)
                if not inst.SoundEmitter:PlayingSound("sniff") then
                    inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/sniff", "sniff")
                end
            end),
            TimeEvent(78*FRAMES, function(inst) inst.SoundEmitter:KillSound("sniff") end),
        },

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

        onexit = function(inst)
            inst.SoundEmitter:KillSound("sniff")
        end,
    },

    State
    {
        name = "exit",
        tags = { "busy" },
        onenter = function(inst)
            inst.Physics:Stop()
            inst:SetAbovePhysics()
            inst.AnimState:PlayAnimation("exit")
            -- if inst.components.burnable:IsBurning() then
            --     inst.components.burnable:Extinguish()
            -- end
        end,

        timeline =
        {
            TimeEvent(8*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/jump") end),
            --TimeEvent(24*FRAMES, function(inst) end),
            TimeEvent(26*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/retract") end),
            TimeEvent(43*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/retract") end),
        },

        events =
        {
            EventHandler("animover", function(inst)
                inst:SetUnderPhysics()
                inst.last_above_time = GetTime()
                inst.sg:GoToState("idle")
            end),
        },
    },
--]]
    State
    {
        name = "idle",
        tags = { "idle", "canrotate" },
        onenter = function(inst, playanim)
            inst.Physics:Stop()
            inst.SoundEmitter:KillSound("move")
            if inst.isunder then
                inst.sg:AddStateTag("noattack")
            end
       
            inst.AnimState:PlayAnimation(inst.isunder and "idle_under" or "idle", true)
            
        end,

        timeline =
        {
            TimeEvent(1*FRAMES, function(inst)
                if not (inst.isunder or inst.SoundEmitter:PlayingSound("sniff")) then
                    inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/sniff", "sniff")
                end
            end),
            TimeEvent(52*FRAMES, function(inst) inst.SoundEmitter:KillSound("sniff") end),
        },
    },

    State
    {
        name = "run_start",
        tags = { "moving", "canrotate", "noattack", "invisible" },

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.AnimState:PlayAnimation("walk_pre")
            if not inst.SoundEmitter:PlayingSound("move") then
                inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/move", "move")
            end
            inst.components.locomotor:WalkForward()
        end,

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

    State
    {
        name = "run",
        tags = { "moving", "canrotate", "noattack", "invisible" },

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.AnimState:PlayAnimation("walk_loop")
            inst.components.locomotor:WalkForward()
        end,

        timeline =
        {
            TimeEvent(0*FRAMES,  SpawnMoveFx),
            TimeEvent(5*FRAMES,  SpawnMoveFx),
            TimeEvent(10*FRAMES, SpawnMoveFx),
            TimeEvent(15*FRAMES, SpawnMoveFx),
            TimeEvent(20*FRAMES, SpawnMoveFx),
            TimeEvent(25*FRAMES, SpawnMoveFx),
        },

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

    State
    {
        name = "run_stop",
        tags = { "canrotate", "noattack", "invisible" },

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.components.locomotor:StopMoving()

            --local should_softstop = false
            --if should_softstop then
                --inst.AnimState:PushAnimation("walk_pst")
            --else
                inst.AnimState:PlayAnimation("walk_pst")
            --end

            inst.SoundEmitter:KillSound("move")
        end,

        events =
        {
            EventHandler("animover", function(inst)
                inst.sg:GoToState("idle")
            end),
        },
    },
--[[    
    State
    {
        name = "action",
        tags = { "canrotate", "noattack" },

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.components.locomotor:StopMoving()

            --local should_softstop = false
            --if should_softstop then
                --inst.AnimState:PushAnimation("walk_pst")
            --else
                inst.AnimState:PlayAnimation("walk_pst")
            --end
            inst:PerformBufferedAction()
            inst.SoundEmitter:KillSound("move")
        end,

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

        onenter = function(inst, playanim)
            inst.Physics:Stop()
            if inst.isunder then
                inst.sg:AddStateTag("noattack")
                inst.AnimState:PlayAnimation("idle_under")
            else
                inst.AnimState:PlayAnimation("idle")
            end
            inst:PerformBufferedAction()
        end,

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

    State
    {
        name = "make_molehill",
        tags = { "busy", "noattack" },

        onenter = function(inst, playanim)
            inst.Physics:Stop()
            inst:SetUnderPhysics()
            inst.AnimState:PlayAnimation("mound")
        end,

        timeline =
        {
            TimeEvent(16*FRAMES, function(inst)
                inst:SetAbovePhysics()
                inst.sg:RemoveStateTag("noattack")
                inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/emerge")
            end),
            TimeEvent(30*FRAMES, function(inst) inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/emerge_voice") end),
        },

        events =
        {
            EventHandler("animover", function(inst, data)
                inst:PerformBufferedAction()
                inst:SetUnderPhysics()
                inst.last_above_time = GetTime()
                inst.sg:GoToState("idle")
            end),
        },
    },
    
    State
    {
        name = "hit",
        tags = { "busy" },

        onenter = function(inst)
            inst.SoundEmitter:KillSound("move")
            inst.SoundEmitter:KillSound("sniff")
            inst.AnimState:PlayAnimation("hit")
            inst.Physics:Stop()
            inst:SetAbovePhysics()
        end,

        events =
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
        },
    },
    
    State
    {
        name = "sleep",
        tags = { "busy", "sleeping" },
        
        onenter = function(inst)
            inst.components.locomotor:StopMoving()
            if inst.isunder then
                inst:SetAbovePhysics()
                inst.AnimState:PlayAnimation("enter")
                inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/emerge")
                inst.AnimState:PushAnimation("sleep_pre", false)
            else
                inst.AnimState:PlayAnimation("sleep_pre")
            end
        end,

        timeline =
        {
            TimeEvent(FRAMES, function(inst)
                inst.SoundEmitter:KillSound("sniff")
                inst.SoundEmitter:KillSound("stunned")
            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 = function(inst)
            inst:SetAbovePhysics()
            inst.AnimState:PlayAnimation("sleep_loop")
        end,

        timeline =
        {
            TimeEvent(27*FRAMES, function(inst)
                if not inst.SoundEmitter:PlayingSound("sleep") then
                    inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/sleep", "sleep")
                end
            end),
            TimeEvent(42*FRAMES, function(inst)
                inst.SoundEmitter:KillSound("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)
            inst:SetAbovePhysics()
            inst.components.locomotor:StopMoving()
            inst.AnimState:PlayAnimation("sleep_pst")
            if inst.components.sleeper ~= nil and inst.components.sleeper:IsAsleep() then
                inst.components.sleeper:WakeUp()
            end
        end,

        timeline =
        {
            TimeEvent(FRAMES, function(inst)
                inst.SoundEmitter:KillSound("sleep")
            end)
        },

        events =
        {
            EventHandler("animover", function(inst)
                inst.sg:GoToState("exit")
            end),
        },
    },
    
    ------------------HOME SLEEPING-----------------

        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.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("idle_under")
            inst.sg:SetTimeout(11 * FRAMES)

            SetSleeperSleepState(inst)
        end,

        ontimeout = function(inst)
            local bufferedaction = inst:GetBufferedAction()
            if bufferedaction == nil then
                inst.AnimState:PlayAnimation("pig_pickup")
                inst.sg:GoToState("idle", true)
                return
            end
            local home = bufferedaction.target
            if home == nil or
                not home:HasTag("molehouse") 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("idle_under")
                inst.sg:GoToState("idle", true)
            else
                inst:PerformBufferedAction()
                inst.SoundEmitter:KillSound("move")
                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 = "home_sleeping",
        tags = { "sleeping", "home" },

        --onenter = onentersleeping,
        
        onenter = function(inst)
                inst.components.locomotor:StopMoving()
                inst.components.health:DoDelta(3.5, false)
                --inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/bearger/sleep")
                inst.AnimState:PlayAnimation("sleep_loop")
            end,

        --timeline = timelines ~= nil and timelines.sleeptimeline or nil,
        timeline =
        {
            TimeEvent(35*FRAMES, function(inst) inst.SoundEmitter:PlaySound(SoundPath(inst, "sleeping")) end ),
        },

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

    State
    {
        name = "home_wake",
        tags = { "busy", "waking", "home" },

        onenter = function(inst)
            if inst.components.locomotor ~= nil then
                inst.components.locomotor:StopMoving()
            end
            --inst.SoundEmitter:PlaySound("dontstarve/creatures/spiderqueen/wakeup")
            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 = timelines ~= nil and timelines.waketimeline or nil,
        
        timeline = 
        {
        TimeEvent(0*FRAMES, function(inst) inst.SoundEmitter:PlaySound(SoundPath(inst, "wakeUp")) end ),
        },
        
        events =
        {
            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end),
        },
    },
--]] 
}

--CommonStates.AddFrozenStates(states)

return StateGraph("molep", states, events, "idle")

Here's the prefab and the stategraph.

Edited by RedHairedHero
Link to comment
Share on other sites

One stuff comes to my mind.

When you activate the cave the behavior is the same as on a dedicated server. The player hosting is not anymore a " mastersim" (as far as I know).

So if you did not plan some client side code in here you'll get a surprise. master_postinit is only executed on the host side if I am correct, so it doesn't exists for the client. If the client is executing some code involving this function => crash

 

EDIT: I just checked, master_postinit is indeed host only. Then I don't know if you took precaution that SetUnderPhysics method is never called on the client side but that's where I would start.

Edited by ZupaleX
Link to comment
Share on other sites

1 minute ago, ZupaleX said:

One stuff comes to my mind.

When you activate the cave the behavior is the same as on a dedicated server. The player hosting is not anymore a " mastersim" (as far as I know).

So if you did not plan some client side code in here you'll get a surprise. master_postinit is only executed on the host side if I am correct, so it doesn't exists for the client. If the client is executing some code involving this function => crash

So is it as simple as moving this into common postinit?

inst.SetUnderPhysics = SetUnderPhysics
    inst.SetAbovePhysics = SetAbovePhysics
	
	MakeCharacterPhysics(inst, 99999, 0.5)
	inst.Physics:SetMass(99999)
    SetUnderPhysics(inst)

 

Link to comment
Share on other sites

AddPhysics is done on the client side as well, so that might work.

EDIT: for characters, AddPhysics is not called directly but implicitly called by MakeCharacterPhysics which is supposed to be in the common_postinit as it should be executed on the client as well.

EDIT2: btw thanks for the spoiler tag but now they are empty :D

Edited by ZupaleX
Link to comment
Share on other sites

15 hours ago, ZupaleX said:

Glad it worked.

I love coding.

So actually I'm running into a new issue. The character transforms and it works great, but pressing the same button is suppose to turn the character back to their previous state, but it's not. Instead when you press it again the only thing that happens is the character hides for a second and a sound plays. Any ideas? I fixed the spoilers.

Edited by RedHairedHero
Link to comment
Share on other sites

Yes. I did not look into the details but here are my concerns.

You call your function burrow, which is the one assigning the tag "underground" or "aboveground" only on the client side. Adding a tag on the client side won't propagate it to the server side. And other things are not propagated currently, like changing the SG, the physics, etc...

In other words the display works for you, but anybody else on the server would still see the human build and probably see your character behaving exactly the same way as if you did not press that button.

You need to send RPC to the server when you press the button so the server actually knows that you expect something to happen.

Link to comment
Share on other sites

Spoiler

local MakePlayerCharacter = require "prefabs/player_common"

local assets = {
    Asset("SCRIPT", "scripts/prefabs/player_common.lua"),
    Asset( "ANIM", "anim/whisper.zip" ),
    Asset( "ANIM", "anim/whisper_furry.zip" ),
    Asset( "ANIM", "anim/whisper_dark.zip" ),
    Asset( "ANIM", "anim/whisper_dark_furry.zip" ),
}

local prefabs = 
{

}

local start_inv = 
{
    "carrot",
    "carrot",
    "carrot",
    "carrot",
}

local function onbecamehuman(inst)
    -- Set speed when reviving from ghost (optional)
    inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 + .25)
end

local function onbecameghost(inst)
   inst.CanExamine = nil
   inst:RemoveTag("underground")
   inst:AddTag("aboveground")
   inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "whisper_speed_mod")
end

local function onload(inst)
    inst:ListenForEvent("ms_respawnedfromghost", onbecamehuman)
    inst:ListenForEvent("ms_becameghost", onbecameghost)

    if inst:HasTag("playerghost") then
        onbecameghost(inst)
    else
        onbecamehuman(inst)
    end
end

local function oneat(inst, food)
    
    local extrahunger = 0
    
    if food.prefab == "carrot" or food.prefab == "carrot_cooked" then
        if food.components.perishable:IsStale() then
            extrahunger = 4
        else if food.components.perishable:IsSpoiled() then
            extrahunger = 2
        else --if it's fresh
            extrahunger = 6
        end
        end
    end
    
    if food and food.components.edible and food.prefab == "carrot"  then
        inst.components.hunger:DoDelta(extrahunger);
    end
    
    if food and food.components.edible and food.prefab == "carrot_cooked"  then
        inst.components.hunger:DoDelta(extrahunger);
    end
    
end

local function OnResetBeard(inst)
    inst:RemoveTag("FURRY")
    if inst:HasTag("DARK") then
        inst.AnimState:SetBuild("whisper_dark")
    else
        inst.AnimState:SetBuild("whisper")
    end
end 
    
local function OnGrowBeard(inst)    
    inst:AddTag("FURRY")
    if inst:HasTag("DARK") then
        inst.AnimState:SetBuild("whisper_dark_furry")
    else
        inst.AnimState:SetBuild("whisper_furry")
    end
    inst.components.beard.bits = 4
end
    
local function CannotExamine(inst)
    return false
end

local function SetUnderPhysics(inst)
    if inst.isunder ~= true then
        inst.isunder = true
        inst.Physics:SetCollisionGroup(COLLISION.CHARACTERS)
        inst.Physics:ClearCollisionMask()
        inst.Physics:CollidesWith(COLLISION.WORLD)
        inst.Physics:CollidesWith(COLLISION.OBSTACLES)
    end
end

local function SetAbovePhysics(inst)
    if inst.isunder ~= false then
        inst.isunder = false
        ChangeToCharacterPhysics(inst)
    end
end

local function OnKeyPressed(inst, data)    
    if data.inst == ThePlayer then
        if data.key == KEY_B and not inst:HasTag("DARK") then
        
            if TheWorld.ismastersim and inst:HasTag("aboveground") then
                    -- Since we are the server, do the action on the server.
                    BufferedAction(inst, inst, ACTIONS.ENTER_BURROW):Do()            
                else       
                    SendRPCToServer(RPC.DoWidgetButtonAction, ACTIONS.ENTER_BURROW.code, inst, ACTIONS.ENTER_BURROW.mod_name)
            end 
            
            if TheWorld.ismastersim and inst:HasTag("underground") then
                    -- Since we are the server, do the action on the server.
                    BufferedAction(inst, inst, ACTIONS.EXIT_BURROW):Do()            
                else       
                    SendRPCToServer(RPC.DoWidgetButtonAction, ACTIONS.EXIT_BURROW.code, inst, ACTIONS.EXIT_BURROW.mod_name)
            end 
            --print("KEY_B has been pressed.")
        end
        
        if data.key == KEY_T then
            if TheWorld.ismastersim then    
                -- Since we are the server, do the action on the server.
                BufferedAction(inst, inst, ACTIONS.DARKSIDE):Do()            
            else       
                SendRPCToServer(RPC.DoWidgetButtonAction, ACTIONS.DARKSIDE.code, inst, ACTIONS.DARKSIDE.mod_name)            
            end        
            --print("KEY_T has been pressed.")            
        end
    end
end

local function controltransform (inst, data)

if not inst:HasTag("playerghost") then
    if inst.components.sanity.current <= 30 and inst:HasTag("DARK") then
        --If in dark mode and sanity falls below 30 force the player back into normal mode
        SendRPCToServer(RPC.DoWidgetButtonAction, ACTIONS.DARKSIDE.code, inst, ACTIONS.DARKSIDE.mod_name)
        inst:AddTag("notransform")
    else if inst.components.sanity.current <= 30 then
        --If the players sanity is below or equal to 30 prevent them from transforming into dark mode
        inst:AddTag("notransform")
    else if inst.components.sanity.current > 30 then
        --If sanity is above 30 allow transformation
        inst:RemoveTag("notransform")
    end
    end
    end
end

end

local function sanityfn(inst)
    local delta = 0
    if TheWorld.state.isday then
            delta = TUNING.SANITY_NIGHT_MID
    end
    return delta
end

-- This initializes for both the server and client. Tags can be added here.

local common_postinit = function(inst)
    inst:AddComponent("keyhandler")
    inst:ListenForEvent("keypressed", OnKeyPressed)
    inst.MiniMapEntity:SetIcon( "whisper.tex" )

    inst:AddTag("aboveground")
    inst:AddTag("bearded")
    
    inst.SetUnderPhysics = SetUnderPhysics
    inst.SetAbovePhysics = SetAbovePhysics
    
    MakeCharacterPhysics(inst, 99999, 0.5)
    inst.Physics:SetMass(99999)
    SetUnderPhysics(inst)
end

-- This initializes for the server only. Components are added here.
local master_postinit = function(inst)
    inst.soundsname = "willow"
    
    inst.components.sanity.custom_rate_fn = sanityfn
    inst.components.eater:SetDiet({ FOODTYPE.VEGGIE }, { FOODTYPE.VEGGIE })
    
    inst:AddComponent("beard")
    inst.components.beard.onreset = OnResetBeard
    inst.components.beard:AddCallback( 7 , OnGrowBeard )
    
    inst.components.sanity.night_drain_mult = 0
    
    inst:AddComponent("lootdropper")
    
    if inst:HasTag("DARK") then
        inst.components.beard.prize = "beardhair"
    else
        inst.components.beard.prize = "manrabbit_tail"
    end
    
    inst.components.eater:SetOnEatFn(oneat)
        
    inst.components.health:SetMaxHealth(200)
    inst.components.hunger:SetMax(150)
    inst.components.sanity:SetMax(150)
    
    inst.components.health:SetAbsorptionAmount(-0.25)
    inst.components.combat.damagemultiplier = 0.75
    
    if inst:HasTag("aboveground") then
        inst.components.hunger.hungerrate = 1 * TUNING.WILSON_HUNGER_RATE 
    else if inst:HasTag("underground") then
        inst.components.hunger.hungerrate = 3 * TUNING.WILSON_HUNGER_RATE 
    end
    end
    
    inst:ListenForEvent("sanitydelta", controltransform)
    
    inst.OnLoad = onload
    inst.OnNewSpawn = onload

    return inst
end

return MakePlayerCharacter("whisper", prefabs, assets, common_postinit, master_postinit, start_inv)

Spoiler

PrefabFiles = {
    "whisper",
    "whisper_none",
}

Assets = {
    Asset( "IMAGE", "images/saveslot_portraits/whisper.tex" ),
    Asset( "ATLAS", "images/saveslot_portraits/whisper.xml" ),

    Asset( "IMAGE", "images/selectscreen_portraits/whisper.tex" ),
    Asset( "ATLAS", "images/selectscreen_portraits/whisper.xml" ),
    
    Asset( "IMAGE", "images/selectscreen_portraits/whisper_silho.tex" ),
    Asset( "ATLAS", "images/selectscreen_portraits/whisper_silho.xml" ),

    Asset( "IMAGE", "bigportraits/whisper.tex" ),
    Asset( "ATLAS", "bigportraits/whisper.xml" ),
    
    Asset( "IMAGE", "images/map_icons/whisper.tex" ),
    Asset( "ATLAS", "images/map_icons/whisper.xml" ),
    
    Asset( "IMAGE", "images/avatars/avatar_whisper.tex" ),
    Asset( "ATLAS", "images/avatars/avatar_whisper.xml" ),
    
    Asset( "IMAGE", "images/avatars/avatar_ghost_whisper.tex" ),
    Asset( "ATLAS", "images/avatars/avatar_ghost_whisper.xml" ),
    
    Asset( "IMAGE", "images/avatars/self_inspect_whisper.tex" ),
    Asset( "ATLAS", "images/avatars/self_inspect_whisper.xml" ),
    
    Asset( "IMAGE", "images/names_whisper.tex" ),
    Asset( "ATLAS", "images/names_whisper.xml" ),
    
    Asset( "IMAGE", "bigportraits/whisper_none.tex" ),
    Asset( "ATLAS", "bigportraits/whisper_none.xml" ),
    
    Asset( "ANIM", "anim/whisper.zip" ),
    Asset( "ANIM", "anim/whisper_furry.zip" ),
    Asset( "ANIM", "anim/whisper_dark.zip" ),
    Asset( "ANIM", "anim/whisper_dark_furry.zip" ),
    
    Asset("SCRIPT", "scripts/prefabs/whisper.lua"),
}

local require = GLOBAL.require
local STRINGS = GLOBAL.STRINGS

modimport("libs/env.lua")

--active key
-- Import the lib use.
modimport("libs/use.lua")

-- Import the mod environment as our environment.
use "libs/mod_env"(env)
-- Imports to keep the keyhandler from working while typing in chat.
use "data/widgets/controls"
use "data/screens/chatinputscreen"
use "data/screens/consolescreen"

-- The character select screen lines
STRINGS.CHARACTER_TITLES.whisper = "The Bunny"
STRINGS.CHARACTER_NAMES.whisper = "Whisper"
STRINGS.CHARACTER_DESCRIPTIONS.whisper = "*Has a dark side \n*Is a Bunnyman \n*Is a vegetarian"
STRINGS.CHARACTER_QUOTES.whisper = "\"Embrace the dark side.\""

-- Custom speech strings
STRINGS.CHARACTERS.WHISPER = require "speech_whisper"

-- The character's name as appears in-game 
STRINGS.NAMES.WHISPER = "Whisper"

AddMinimapAtlas("images/map_icons/whisper.xml")

-- Add mod character to mod character list. Also specify a gender. Possible genders are MALE, FEMALE, ROBOT, NEUTRAL, and PLURAL.
AddModCharacter("whisper", "FEMALE")

local transform = State(
        {
        name = "transforming",
        tags = { "busy", "pausepredict", "nomorph", "nodangle" },

        onenter = function(inst, cb)
        
            inst.components.locomotor:StopMoving()
            inst.sg.statemem.cb = cb

            inst.AnimState:OverrideSymbol("shadow_hands", "shadow_skinchangefx", "shadow_hands")
            inst.AnimState:OverrideSymbol("shadow_ball", "shadow_skinchangefx", "shadow_ball")
            inst.AnimState:OverrideSymbol("splode", "shadow_skinchangefx", "splode")

            inst.AnimState:PlayAnimation("skin_change", false)--true loops

            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
            end
        end,

        timeline =
        {
            --frame 42, hidden
            TimeEvent(62 * FRAMES, function(inst)
                if inst.sg.statemem.cb ~= nil then
                    inst.sg.statemem.cb()
                    inst.sg.statemem.cb = nil
                end
            end),
        },

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

        onexit = function(inst)
            if inst:HasTag("FURRY") and inst:HasTag("DARK") then
                inst.AnimState:SetBuild("whisper_dark_furry")
            else if not inst:HasTag("FURRY") and inst:HasTag("DARK") then
                inst.AnimState:SetBuild("whisper_dark")
            else if inst:HasTag("FURRY") and not inst:HasTag("DARK") then
                inst.AnimState:SetBuild("whisper_furry")
            else
                inst.AnimState:SetBuild("whisper")
            end
            end
            end
            
            if inst:HasTag("DARK") then
                inst.components.combat.damagemultiplier = 1.25
                inst.components.sanity.dapperness = (TUNING.CRAZINESS_SMALL)--SMALL Night Armour, MED is Dark Sword
                inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 )
            else
                inst.components.combat.damagemultiplier = 1.0 
                inst.components.sanity.dapperness = 0
                inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 + .25)
             end
            
            if inst.sg.statemem.cb ~= nil then
                -- in case of interruption
                inst.sg.statemem.cb()
                inst.sg.statemem.cb = nil
            end
            inst.AnimState:OverrideSymbol("shadow_hands", "shadow_hands", "shadow_hands")
            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,
    }
)
AddStategraphState("wilson", transform)

local DARKSIDE = GLOBAL.Action()
    DARKSIDE.str = "Darkside"
    DARKSIDE.id = "DARKSIDE"
    DARKSIDE.fn = function(act)
    
    if not act.target:HasTag("DARK") and not act.target:HasTag("notransform") then--if the user is normal and can transform, change to dark
        act.target:AddTag("DARK")
        act.target.SoundEmitter:PlaySound("dontstarve/creatures/werepig/transformToPig")
        act.target.sg:GoToState("transforming")
        act.target.components.beard.prize = "beardhair"
    else if act.target:HasTag("DARK") then--if user is dark, change to normal
        act.target:RemoveTag("DARK")
        act.target.SoundEmitter:PlaySound("dontstarve/creatures/werepig/transformToPig")
        act.target.sg:GoToState("transforming")
        act.target.components.beard.prize = "manrabbit_tail"
    end
    end

end

AddAction(DARKSIDE)

local enter_burrow = State(
        {
        name = "enter_burrow",
        tags = { "busy", "pausepredict", "nomorph", "nodangle" },

        onenter = function(inst, cb)
        
            inst.components.locomotor:StopMoving()
            inst.sg.statemem.cb = cb

            inst.AnimState:PlayAnimation("jump", false)
            inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/jump")
            
            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
            end
        end,

        timeline =
        {
            TimeEvent(15 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    inst.Physics:Stop()
                end
            end),
        },

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

        onexit = function(inst)
            
            inst:RemoveTag("aboveground")
            inst:AddTag("underground")
            inst.AnimState:SetBank("mole")
            inst.AnimState:SetBuild("mole_build")
            inst:SetStateGraph("SGburrow")
            inst.CanExamine = CannotExamine
            inst.components.locomotor.fasteronroad = false
            --print("underground")
            
            if inst.sg.statemem.cb ~= nil then
                -- in case of interruption
                inst.sg.statemem.cb()
                inst.sg.statemem.cb = nil
            end
            
            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,
    }
)
AddStategraphState("wilson", enter_burrow)

local ENTER_BURROW = GLOBAL.Action()
    ENTER_BURROW.str = "Enter Burrow"
    ENTER_BURROW.id = "ENTER_BURROW"
    ENTER_BURROW.fn = function(act)

    act.target.sg:GoToState("enter_burrow")

end

AddAction(ENTER_BURROW)

local exit_burrow = State(
        {
        name = "exit_burrow",
        tags = { "busy", "pausepredict", "nomorph", "nodangle" },

        onenter = function(inst, cb)
            inst.components.locomotor:StopMoving()
            inst.sg.statemem.cb = cb

            inst.AnimState:SetBank("wilson")
            if inst:HasTag("FURRY") then
                inst.AnimState:SetBuild("whisper_furry")
            else
                inst.AnimState:SetBuild("whisper")
            end
            inst.SoundEmitter:KillSound("move")
            
            inst.AnimState:PlayAnimation("jumpout")
            inst.SoundEmitter:PlaySound("dontstarve/creatures/mandrake/plant_dirt")
            ChangeToCharacterPhysics(inst)

            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
            end
            print ("onenter complete")
        end,

        timeline =
        {
            TimeEvent(10 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(3, 0, 0)
                end
            end),
            TimeEvent(15 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(2, 0, 0)
                end
            end),
            TimeEvent(15.2 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    if inst.sg.statemem.isphysicstoggle then
                        ToggleOnPhysics(inst)
                    end
                    inst.SoundEmitter:PlaySound("dontstarve/movement/bodyfall_dirt")
                end
            end),

            TimeEvent(17 * FRAMES, function(inst)
                inst.Physics:SetMotorVel(inst.sg.statemem.heavy and .5 or 1, 0, 0)
            end),
            TimeEvent(18 * FRAMES, function(inst)
                inst.Physics:Stop()
            end),
            print ("timeline complete")
        },

        events =
        {
            EventHandler("animqueueover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst:SetStateGraph("SGwilson")
                    inst.sg:GoToState("idle")
                end
            end),
        },

        onexit = function(inst)

            inst:RemoveTag("underground")
            inst:AddTag("aboveground")
            inst.CanExamine = nil
            inst.components.locomotor.fasteronroad = true
            --print("aboveground")
            
            if inst.sg.statemem.cb ~= nil then
                -- in case of interruption
                inst.sg.statemem.cb()
                inst.sg.statemem.cb = nil
            end
            
            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:EnableMapControls(true)
                inst.components.playercontroller:Enable(true)
            end
            inst.components.inventory:Show()
            inst:ShowActions(true)
            print ("onexit complete")
            
        end,
    }
)
AddStategraphState("burrow", exit_burrow)

local EXIT_BURROW = GLOBAL.Action()
    EXIT_BURROW.str = "Exit Burrow"
    EXIT_BURROW.id = "EXIT_BURROW"
    EXIT_BURROW.fn = function(act)

    act.target.sg:GoToState("exit_burrow")
    
end

AddAction(EXIT_BURROW)

Spoiler

require("stategraphs/commonstates")

local WALK_SPEED = 4
local RUN_SPEED = 7

local events=
{
    EventHandler("death", function(inst) inst.sg:GoToState("death") end),
    --CommonHandlers.OnLocomote(true,false),
    EventHandler("locomote", function(inst, data)
        if inst.sg:HasStateTag("busy") or inst:HasTag("busy") then
            return
        end
        local is_moving = inst.sg:HasStateTag("moving")
        local should_move = inst.components.locomotor:WantsToMoveForward()

        if inst.sg:HasStateTag("bedroll") or inst.sg:HasStateTag("tent") or inst.sg:HasStateTag("waking") then -- wakeup on locomote
            if inst.sleepingbag ~= nil and inst.sg:HasStateTag("sleeping") then
                inst.sleepingbag.components.sleepingbag:DoWakeUp()
                inst.sleepingbag = nil
            end
        elseif is_moving and not should_move then
            inst.sg:GoToState("run_stop")
        elseif not is_moving and should_move then
            inst.sg:GoToState("run_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),
}

local function SpawnMoveFx(inst)
    SpawnPrefab("mole_move_fx").Transform:SetPosition(inst.Transform:GetWorldPosition())
end

local states=
{
  
    State{
        name = "death",
        tags = {"busy", "pausepredict", "nomorph" },
        
        onenter = function(inst)
            assert(inst.deathcause ~= nil, "Entered death state without cause.")

            ClearStatusAilments(inst)

            inst.components.locomotor:Stop()
            inst.components.locomotor:Clear()
            inst:ClearBufferedAction()
            
            inst.SoundEmitter:KillSound("move")
            inst.AnimState:SetBank("wilson")
            if inst:HasTag("FURRY") then
                inst.AnimState:SetBuild("whisper_furry")
            else
                inst.AnimState:SetBuild("whisper")
            end
            inst.SoundEmitter:KillSound("move")
            inst:SetStateGraph("SGwilson")
            inst.SoundEmitter:PlaySound("dontstarve/wilson/death")
            inst.AnimState:Hide("swap_arm_carry")
            inst.AnimState:PlayAnimation("death")
            
            inst.Physics:Stop()
            RemovePhysicsColliders(inst)      
         
            if inst.components.playercontroller ~= nil then
               inst.components.playercontroller:RemotePausePrediction()
               inst.components.playercontroller:Enable(false)
            end
            
            --Don't process other queued events if we died this frame
            inst.sg:ClearBufferedEvents()
        end,
        
        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst:PushEvent(inst.ghostenabled and "makeplayerghost" or "playerdied", { skeleton = true })
                end
            end),
        },

        

    },    

    State
    {
        name = "idle",
        tags = { "idle", "canrotate", "noattack" },
        onenter = function(inst, playanim)
            inst.Physics:Stop()
            inst.SoundEmitter:KillSound("move")
            if inst.isunder then
                inst.sg:AddStateTag("noattack")
            end
       
            if playanim then
                inst.AnimState:PlayAnimation(playanim)
                inst.AnimState:PushAnimation("idle_under", true)
            else
                inst.AnimState:PlayAnimation("idle_under", true)
            end
            
        end,

    },

    State
    {
        name = "run_start",
        tags = { "moving", "running", "canrotate", "noattack", "autopredict"}, -- "invisible"

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.AnimState:PlayAnimation("walk_pre")
            if not inst.SoundEmitter:PlayingSound("move") then
                inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/move", "move")
            end
            inst.components.locomotor:WalkForward()
        end,

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

    State
    {
        name = "run",
        tags = { "moving", "canrotate", "noattack", "autopredict"}, -- "invisible"

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.AnimState:PlayAnimation("walk_loop")
            inst.components.locomotor:WalkForward()
        end,

        timeline =
        {
            TimeEvent(0*FRAMES,  SpawnMoveFx),
            TimeEvent(5*FRAMES,  SpawnMoveFx),
            TimeEvent(10*FRAMES, SpawnMoveFx),
            TimeEvent(15*FRAMES, SpawnMoveFx),
            TimeEvent(20*FRAMES, SpawnMoveFx),
            TimeEvent(25*FRAMES, SpawnMoveFx),
        },

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

    State
    {
        name = "run_stop",
        tags = { "canrotate", "noattack", "idle", "autopredict"}, -- "invisible"

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.components.locomotor:StopMoving()
            inst.AnimState:PlayAnimation("walk_pst")
            inst.SoundEmitter:KillSound("move")
        end,

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


return StateGraph("burrow", states, events, "idle")

So I've updated it to happen as a State now instead, but once again I'm running into a couple problems. 

So enter burrow state works fine client and server, but server side moving with the character is off, it seems the character moves faster then the actual burrowing animation and you get some rubberbanding.

The second thing is the exit burrow state sort of works client side, the animation plays but the character doesn't return back to normal, just continues burrowing. The exit burrow was added to the SGmolep stategraph so it could be used, but I feel like that might be what's causing the issue.

I resolved the second issue I was having and edited the spoilers to reflect the updates.

Any ideas on what I'm doing wrong?

Edited by RedHairedHero
Link to comment
Share on other sites

I am unsure about what you are doing with the RPC. Instead of trying to hack the core RPC, why don't you use the functions planned for the modders? AddModRPCHandler

and

SendModRPCToServer

What you describing sounds like there is an issue at some point with the communication between the host and the client. So if the host has different info compared to the client regarding the speed it will create what you are describing.

There might be something else going on here but first I would fix  the way you are using RPCs.

Link to comment
Share on other sites

Spoiler

PrefabFiles = {
    "whisper",
    "whisper_none",
}

Assets = {
    Asset( "IMAGE", "images/saveslot_portraits/whisper.tex" ),
    Asset( "ATLAS", "images/saveslot_portraits/whisper.xml" ),

    Asset( "IMAGE", "images/selectscreen_portraits/whisper.tex" ),
    Asset( "ATLAS", "images/selectscreen_portraits/whisper.xml" ),
    
    Asset( "IMAGE", "images/selectscreen_portraits/whisper_silho.tex" ),
    Asset( "ATLAS", "images/selectscreen_portraits/whisper_silho.xml" ),

    Asset( "IMAGE", "bigportraits/whisper.tex" ),
    Asset( "ATLAS", "bigportraits/whisper.xml" ),
    
    Asset( "IMAGE", "images/map_icons/whisper.tex" ),
    Asset( "ATLAS", "images/map_icons/whisper.xml" ),
    
    Asset( "IMAGE", "images/avatars/avatar_whisper.tex" ),
    Asset( "ATLAS", "images/avatars/avatar_whisper.xml" ),
    
    Asset( "IMAGE", "images/avatars/avatar_ghost_whisper.tex" ),
    Asset( "ATLAS", "images/avatars/avatar_ghost_whisper.xml" ),
    
    Asset( "IMAGE", "images/avatars/self_inspect_whisper.tex" ),
    Asset( "ATLAS", "images/avatars/self_inspect_whisper.xml" ),
    
    Asset( "IMAGE", "images/names_whisper.tex" ),
    Asset( "ATLAS", "images/names_whisper.xml" ),
    
    Asset( "IMAGE", "bigportraits/whisper_none.tex" ),
    Asset( "ATLAS", "bigportraits/whisper_none.xml" ),
    
    Asset( "ANIM", "anim/whisper.zip" ),
    Asset( "ANIM", "anim/whisper_furry.zip" ),
    Asset( "ANIM", "anim/whisper_dark.zip" ),
    Asset( "ANIM", "anim/whisper_dark_furry.zip" ),
    
    Asset("SCRIPT", "scripts/prefabs/whisper.lua"),
}

local require = GLOBAL.require
local STRINGS = GLOBAL.STRINGS

modimport("libs/env.lua")

--active key
-- Import the lib use.
modimport("libs/use.lua")

-- Import the mod environment as our environment.
use "libs/mod_env"(env)
-- Imports to keep the keyhandler from working while typing in chat.
use "data/widgets/controls"
use "data/screens/chatinputscreen"
use "data/screens/consolescreen"

-- The character select screen lines
STRINGS.CHARACTER_TITLES.whisper = "The Bunny"
STRINGS.CHARACTER_NAMES.whisper = "Whisper"
STRINGS.CHARACTER_DESCRIPTIONS.whisper = "*Has a dark side \n*Is a Bunny \n*Is a vegetarian"
STRINGS.CHARACTER_QUOTES.whisper = "\"Embrace the dark side.\""

-- Custom speech strings
STRINGS.CHARACTERS.WHISPER = require "speech_whisper"

-- The character's name as appears in-game 
STRINGS.NAMES.WHISPER = "Whisper"

AddMinimapAtlas("images/map_icons/whisper.xml")

-- Add mod character to mod character list. Also specify a gender. Possible genders are MALE, FEMALE, ROBOT, NEUTRAL, and PLURAL.
AddModCharacter("whisper", "FEMALE")

local function IsDefaultScreen() 
    if GLOBAL.TheFrontEnd:GetActiveScreen() and GLOBAL.TheFrontEnd:GetActiveScreen().name and type(GLOBAL.TheFrontEnd:GetActiveScreen().name) == "string" and GLOBAL.TheFrontEnd:GetActiveScreen().name == "HUD" then
        return true
    else
        return false
    end
end

local function Transform(player)    
    if not player:HasTag("DARK") and not player:HasTag("burrow") then
        player:AddTag("DARK")
        player:AddTag("scarytoprey")
        player.SoundEmitter:PlaySound("dontstarve/creatures/werepig/transformToPig")
        player.sg:GoToState("transforming")
        player.components.sanity:DoDelta(-10)
    else if player:HasTag("DARK") then
        player:RemoveTag("DARK")
        player:RemoveTag("scarytoprey")
        player.SoundEmitter:PlaySound("dontstarve/creatures/werepig/transformToPig")
        player.sg:GoToState("transforming")
        player.components.sanity:DoDelta(-10)
    end
    end
end

local function Burrow(player)    
    if not player:HasTag("burrow") and not player:HasTag("DARK") then
        player:AddTag("burrow")
        player.components.grue:AddImmunity("invincible")
        player.components.hunger:DoDelta(-10)
        player.components.hunger:SetRate(3 * TUNING.WILSON_HUNGER_RATE)
        player.sg:GoToState("enter_burrow")
    else if player:HasTag("burrow") then
        player:RemoveTag("burrow")
        player.components.hunger:SetRate(1 * TUNING.WILSON_HUNGER_RATE)
        player.sg:GoToState("exit_burrow")
        player.components.grue:RemoveImmunity("invincible")
    end
    end
end

AddModRPCHandler(modname, "Transform", Transform)
AddModRPCHandler(modname, "Burrow", Burrow)

local function SendTransformRPC()    
    SendModRPCToServer(MOD_RPC[modname]["Transform"])
end


local function SendBurrowRPC()    
    SendModRPCToServer(MOD_RPC[modname]["Burrow"])
end

GLOBAL.TheInput:AddKeyDownHandler(116, function()
local player = GLOBAL.ThePlayer

if player and not player.HUD:IsChatInputScreenOpen() and not player.HUD:IsConsoleScreenOpen() and IsDefaultScreen() then
 
 SendModRPCToServer(MOD_RPC[modname]["Transform"])
 end 
 end
 )

GLOBAL.TheInput:AddKeyDownHandler(98, function()
local player = GLOBAL.ThePlayer

if player and not player.HUD:IsChatInputScreenOpen() and not player.HUD:IsConsoleScreenOpen() and IsDefaultScreen() then
 
 SendModRPCToServer(MOD_RPC[modname]["Burrow"])
 end 
 end
 )

local transform = State(
        {
        name = "transforming",
        tags = { "busy", "pausepredict", "nomorph", "nodangle" },

        onenter = function(inst, cb)
        
            if inst.components.inventory:IsHeavyLifting() then
                inst.components.inventory:DropItem(
                inst.components.inventory:Unequip(EQUIPSLOTS.BODY),
                true,
                true
                )
            end
            
            inst.components.locomotor:StopMoving()
            inst.sg.statemem.cb = cb

            inst.AnimState:OverrideSymbol("shadow_hands", "shadow_skinchangefx", "shadow_hands")
            inst.AnimState:OverrideSymbol("shadow_ball", "shadow_skinchangefx", "shadow_ball")
            inst.AnimState:OverrideSymbol("splode", "shadow_skinchangefx", "splode")

            inst.AnimState:PlayAnimation("skin_change", false)--true loops

            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
            end
        end,

        timeline =
        {
            TimeEvent(62 * FRAMES, function(inst)
                if inst.sg.statemem.cb ~= nil then
                    inst.sg.statemem.cb()
                    inst.sg.statemem.cb = nil
                end
            end),
        },

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

        onexit = function(inst)
            if inst:HasTag("FURRY") and inst:HasTag("DARK") then
                inst.AnimState:SetBuild("whisper_dark_furry")
            else if not inst:HasTag("FURRY") and inst:HasTag("DARK") then
                inst.AnimState:SetBuild("whisper_dark")
            else if inst:HasTag("FURRY") and not inst:HasTag("DARK") then
                inst.AnimState:SetBuild("whisper_furry")
            else
                inst.AnimState:SetBuild("whisper")
            end
            end
            end
            
            if inst:HasTag("DARK") then
                inst.components.combat.damagemultiplier = 1.25
                inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 )
            else
                inst.components.combat.damagemultiplier = 1.0 
                inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 + .25)
             end
            
            if inst.sg.statemem.cb ~= nil then
                -- in case of interruption
                inst.sg.statemem.cb()
                inst.sg.statemem.cb = nil
            end
            inst.AnimState:OverrideSymbol("shadow_hands", "shadow_hands", "shadow_hands")
            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:EnableMapControls(true)
                inst.components.playercontroller:Enable(true)
            end
            inst:ShowActions(true)
            
            
        end,
    }
)
AddStategraphState("wilson", transform)

local enter_burrow = State(
        {
        name = "enter_burrow",
        tags = { "busy", "pausepredict", "nomorph", "nodangle" },

        onenter = function(inst, cb)
            
            if inst.components.inventory:IsHeavyLifting() then
                inst.components.inventory:DropItem(
                inst.components.inventory:Unequip(EQUIPSLOTS.BODY),
                true,
                true
                )
            end
            
            inst.components.locomotor:StopMoving()
            inst.sg.statemem.cb = cb

            inst.AnimState:PlayAnimation("jump", false)
            inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/jump")
            
            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
            end
        end,

        timeline =
        {
            TimeEvent(15 * FRAMES, function(inst)
                inst.Physics:Stop()    
            end),
        },

        events =
        {
            EventHandler("animqueueover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst.AnimState:SetBank("mole")
                    inst.AnimState:SetBuild("mole_build")
                    inst:SetStateGraph("SGburrow")
                    inst.sg:GoToState("idle")
                end
            end),
        },

        onexit = function(inst)
            
            inst.CanExamine = CannotExamine
            
            if inst.sg.statemem.cb ~= nil then
                -- in case of interruption
                inst.sg.statemem.cb()
                inst.sg.statemem.cb = nil
            end
            
            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:EnableMapControls(true)
                inst.components.playercontroller:Enable(true)
            end
            inst:ShowActions(false)

        end,
    }
)
AddStategraphState("wilson", enter_burrow)

local exit_burrow = State(
        {
        name = "exit_burrow",
        tags = { "busy", "pausepredict", "nomorph", "nodangle" },

        onenter = function(inst, cb)
            inst.components.locomotor:StopMoving()
            inst.sg.statemem.cb = cb

            inst.AnimState:SetBank("wilson")
            if inst:HasTag("FURRY") then
                inst.AnimState:SetBuild("whisper_furry")
            else
                inst.AnimState:SetBuild("whisper")
            end
            inst.SoundEmitter:KillSound("move")
            
            inst.AnimState:PlayAnimation("jumpout")
            inst.SoundEmitter:PlaySound("dontstarve/creatures/mandrake/plant_dirt")
            ChangeToCharacterPhysics(inst)

            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:RemotePausePrediction()
            end
        end,

        timeline =
        {
            TimeEvent(10 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(3, 0, 0)
                end
            end),
            TimeEvent(15 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    inst.Physics:SetMotorVel(2, 0, 0)
                end
            end),
            TimeEvent(15.2 * FRAMES, function(inst)
                if not inst.sg.statemem.heavy then
                    if inst.sg.statemem.isphysicstoggle then
                        ToggleOnPhysics(inst)
                    end
                    inst.SoundEmitter:PlaySound("dontstarve/movement/bodyfall_dirt")
                end
            end),

            TimeEvent(17 * FRAMES, function(inst)
                inst.Physics:SetMotorVel(inst.sg.statemem.heavy and .5 or 1, 0, 0)
            end),
            TimeEvent(18 * FRAMES, function(inst)
                inst.Physics:Stop()
            end),
        },

        events =
        {
            EventHandler("animqueueover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst:SetStateGraph("SGwilson")
                    inst.sg:GoToState("idle")
                end
            end),
        },

        onexit = function(inst)

            inst.CanExamine = nil
            
            if inst.sg.statemem.cb ~= nil then
                -- in case of interruption
                inst.sg.statemem.cb()
                inst.sg.statemem.cb = nil
            end
            
            if inst.components.playercontroller ~= nil then
                inst.components.playercontroller:EnableMapControls(true)
                inst.components.playercontroller:Enable(true)
            end
            inst:ShowActions(true)

        end,
    }
)
AddStategraphState("burrow", exit_burrow)

Spoiler

local MakePlayerCharacter = require "prefabs/player_common"

local assets = {
    Asset("SCRIPT", "scripts/prefabs/player_common.lua"),
    Asset( "ANIM", "anim/whisper.zip" ),
    Asset( "ANIM", "anim/whisper_furry.zip" ),
    Asset( "ANIM", "anim/whisper_dark.zip" ),
    Asset( "ANIM", "anim/whisper_dark_furry.zip" ),
}

local prefabs = 
{

}

local start_inv = 
{
    "carrot",
    "carrot",
    "carrot",
    "carrot",
}

local function onbecamehuman(inst)
    -- Set speed when reviving from ghost (optional)
    inst.components.locomotor:SetExternalSpeedMultiplier(inst, "whisper_speed_mod", 1 + .25 )
end

local function onbecameghost(inst)
   inst.CanExamine = nil
   inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "whisper_speed_mod")
end

local function onload(inst)
    inst:ListenForEvent("ms_respawnedfromghost", onbecamehuman)
    inst:ListenForEvent("ms_becameghost", onbecameghost)
    inst.components.hunger:SetRate(1 * TUNING.WILSON_HUNGER_RATE)

    if inst:HasTag("playerghost") then
        onbecameghost(inst)
    else
        onbecamehuman(inst)
    end
end

local function oneat(inst, food)
    
    local extrahunger = 0
    
    if food.prefab == "carrot" or food.prefab == "carrot_cooked" then
        if food.components.perishable:IsStale() then
            extrahunger = 4
        else if food.components.perishable:IsSpoiled() then
            extrahunger = 2
        else --if it's fresh
            extrahunger = 6
        end
        end
    end
    
    if food and food.components.edible and food.prefab == "carrot"  then
        inst.components.hunger:DoDelta(extrahunger)
    end
    
    if food and food.components.edible and food.prefab == "carrot_cooked"  then
        inst.components.hunger:DoDelta(extrahunger)
    end
    
end
    
local function CannotExamine(inst)
    return false
end

local function SetUnderPhysics(inst)
    if inst.isunder ~= true then
        inst.isunder = true
        inst.Physics:SetCollisionGroup(COLLISION.CHARACTERS)
        inst.Physics:ClearCollisionMask()
        inst.Physics:CollidesWith(COLLISION.WORLD)
        inst.Physics:CollidesWith(COLLISION.OBSTACLES)
    end
end

local function SetAbovePhysics(inst)
    if inst.isunder ~= false then
        inst.isunder = false
        ChangeToCharacterPhysics(inst)
    end
end

local function sanityfn(inst)
    local delta = 0
    if TheWorld.state.isday then
            delta = TUNING.SANITY_NIGHT_MID
    end
    return delta
end

-- This initializes for both the server and client. Tags can be added here.

local common_postinit = function(inst)
    inst.MiniMapEntity:SetIcon( "whisper.tex" )
    
    inst:RemoveTag("scarytoprey")
    inst:AddTag("mole")
        
    inst.SetUnderPhysics = SetUnderPhysics
    inst.SetAbovePhysics = SetAbovePhysics
    
    MakeCharacterPhysics(inst, 99999, 0.5)
    inst.Physics:SetMass(99999)
    SetUnderPhysics(inst)
end

-- This initializes for the server only. Components are added here.
local master_postinit = function(inst)
    inst.soundsname = "willow"
    
    inst.components.sanity.custom_rate_fn = sanityfn
    inst.components.eater:SetDiet({ FOODTYPE.VEGGIE }, { FOODTYPE.VEGGIE })
    
    inst.components.sanity.night_drain_mult = 0
        
    inst.components.eater:SetOnEatFn(oneat)
        
    inst.components.health:SetMaxHealth(200)
    inst.components.hunger:SetMax(150)
    inst.components.sanity:SetMax(150)
    
    inst.components.health:SetAbsorptionAmount(-0.25)
    inst.components.combat.damagemultiplier = 0.75
        
    inst.OnLoad = onload
    inst.OnNewSpawn = onload

    return inst
end

return MakePlayerCharacter("whisper", prefabs, assets, common_postinit, master_postinit, start_inv)

Spoiler

require("stategraphs/commonstates")

local WALK_SPEED = 4
local RUN_SPEED = 7

local events=
{
    EventHandler("death", function(inst) inst.sg:GoToState("death") end),
    EventHandler("locomote", function(inst, data)
        if inst.sg:HasStateTag("busy") or inst:HasTag("busy") then
            return
        end
        local is_moving = inst.sg:HasStateTag("moving")
        local should_move = inst.components.locomotor:WantsToMoveForward()
            
        if is_moving and not should_move then
            inst.sg:GoToState("run_stop")
        elseif not is_moving and should_move then
            inst.sg:GoToState("run_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),
}

local function SpawnMoveFx(inst)
    SpawnPrefab("mole_move_fx").Transform:SetPosition(inst.Transform:GetWorldPosition())
end

local states=
{
  
    State{
        name = "death",
        tags = {"busy", "pausepredict", "nomorph" },
        
        onenter = function(inst)
            assert(inst.deathcause ~= nil, "Entered death state without cause.")

            inst.components.locomotor:Stop()
            inst.components.locomotor:Clear()
            inst:ClearBufferedAction()
            
            inst.SoundEmitter:KillSound("move")
            inst.AnimState:SetBank("wilson")
            if inst:HasTag("FURRY") then
                inst.AnimState:SetBuild("whisper_furry")
            else
                inst.AnimState:SetBuild("whisper")
            end
            inst.SoundEmitter:KillSound("move")
            inst:SetStateGraph("SGwilson")
            inst.sg:GoToState("death")

            inst.Physics:Stop()
            RemovePhysicsColliders(inst)      
         
            if inst.components.playercontroller ~= nil then
               inst.components.playercontroller:RemotePausePrediction()
               inst.components.playercontroller:Enable(false)
            end
            
            --Don't process other queued events if we died this frame
            inst.sg:ClearBufferedEvents()
        end,
        
        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst:PushEvent(inst.ghostenabled and "makeplayerghost" or "playerdied", { skeleton = true })
                end
            end),
        },
    },    

    State
    {
        name = "idle",
        tags = { "idle", "canrotate", "noattack" },
        onenter = function(inst, pushanim)
            --inst.entity:SetIsPredictingMovement(false)
            inst.Physics:Stop()
            inst.SoundEmitter:KillSound("move")
       
            if pushanim then
                inst.AnimState:PlayAnimation(pushanim)
                inst.AnimState:PushAnimation("idle_under", true)
            else
                inst.AnimState:PlayAnimation("idle_under", true)
            end
            
            end,
            
            onexit = function(inst)
            inst.entity:SetIsPredictingMovement(true)
            end,

    },

    State
    {
        name = "run_start",
        tags = { "moving", "canrotate", "noattack", "invisible" },

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.components.locomotor:WalkForward()
            inst.AnimState:PlayAnimation("walk_pre")
            if not inst.SoundEmitter:PlayingSound("move") then
                inst.SoundEmitter:PlaySound("dontstarve_DLC001/creatures/mole/move", "move")
            end
        end,
        
        onupdate = function(inst)
        inst.components.locomotor:WalkForward()
        end,

        events =
        {
            EventHandler("animover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst.sg:GoToState("run")
                end
            end),
        }
    },

    State
    {
        name = "run",
        tags = { "moving", "running", "canrotate", "noattack" },

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.components.locomotor:WalkForward()
            inst.AnimState:PlayAnimation("walk_loop", true)
        end,
        
        onupdate = function(inst)
            inst.components.locomotor:WalkForward()
        end,

        timeline =
        {
            TimeEvent(0*FRAMES,  SpawnMoveFx),
            TimeEvent(5*FRAMES,  SpawnMoveFx),
            TimeEvent(10*FRAMES, SpawnMoveFx),
            TimeEvent(15*FRAMES, SpawnMoveFx),
            TimeEvent(20*FRAMES, SpawnMoveFx),
            TimeEvent(25*FRAMES, SpawnMoveFx),
        },

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

    State
    {
        name = "run_stop",
        tags = { "canrotate", "noattack", "invisible" },

        onenter = function(inst)
            inst:SetUnderPhysics()
            inst.components.locomotor:StopMoving()
            
            inst.AnimState:PlayAnimation("walk_pst")
            inst.SoundEmitter:KillSound("move")
        end,

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


return StateGraph("burrow", states, events, "idle")

I've updated the code and spoilers with the Mod RPC recommendation. So looking a bit further into the client side there's a few things I'm noticing.

The first was the issue I mentioned earlier where the character is faster then the burrow effects and rubberbands.

The second issue I'm seeing is that it appears the animations from the stategraph aren't playing while in client. "idle_under", "walk_pst", "walk_pre", and "walk_loop".

Update: It seems the issue occurs if the player has Lag Compensation turned on in the settings, turning it off fixed both the speed and animation. 

Edited by RedHairedHero
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...