Jump to content

[HELP] Modifying StateGraph PostInit (crashes)


Recommended Posts

I am trying to modify a single line in the timeline of a state on a character, postinit. I didn't see any way in modutils to do that, so I tried just using AddStateGraphPostInit to replace the entire state with a version that is identical except that one line. However, I have run into the following issue.

The State contains a reference to CommonHandlers. When trying to load with the mod enabled, it crashes with a log error "attempt to index global 'CommonHandlers' (a nil value)". Pretty basic error when running a postinit from modmain, so I changed it to GLOBAL.CommonHandlers. Except now it crashes with the log error "variable 'CommonHandlers' is not declared".

I have no idea what to do from this point, and haven't found anything via searching. If anyone knows how to fix this, or a way to just replace the one line/part I need without replacing the entire state, I would really appreciate it. Thanks!

EDIT: I also tried using inst:SetStateGraph in a prefab postinit to make the player use my own custom version of the stategraph that has that line changed. However, it simply didn't work; the game did not use the custom stategraph (everything else in the prefab postinit works fine though). I've used SetStateGraph for mobs, but figured it just doesn't work for players. If there's a way to do this, that would also be an acceptable solution for me.

Edited by meltorefas
Link to comment
Share on other sites

Not much to work with so I'll take a stab in the dark,do you have all these lines:

local require = GLOBAL.require
local State = GLOBAL.State
local Action = GLOBAL.Action
local ActionHandler = GLOBAL.ActionHandler
local TimeEvent = GLOBAL.TimeEvent
local EventHandler = GLOBAL.EventHandler
local ACTIONS = GLOBAL.ACTIONS
local FRAMES = GLOBAL.FRAMES

before the stategraph code?

Link to comment
Share on other sites

Okay, sorry for the long delay in replying. I was super tired when I posted this and... forgot I had done it. >.> Was taking a break from DST to play something else, but when I came back and tried to tackle this problem again I finally remembered. Also, sorry for not posting literally any code. I should probably have waited until after I slept, lol. Thanks for taking a crack at it anyways!

So, your solution unfortunately didn't work, but it DID let me remove all the GLOBAL. from before the various global functions, which was nice (and blindingly obvious in retrospect, I feel silly). But yeah, still crashes with one of the two CommonHandlers related errors from above (depending on whether I include a GLOBAL. or not). Here is the actual (relevant) *code* this time, though:

Spoiler

PrefabFiles = {
    
}

Assets = {

}

local require = GLOBAL.require
local State = GLOBAL.State
local Action = GLOBAL.Action
local ActionHandler = GLOBAL.ActionHandler
local TimeEvent = GLOBAL.TimeEvent
local EventHandler = GLOBAL.EventHandler
local ACTIONS = GLOBAL.ACTIONS
local FRAMES = GLOBAL.FRAMES
local DEGREES = GLOBAL.DEGREES
local Vector3 = GLOBAL.Vector3
local CommonHandlers = GLOBAL.CommonHandlers --trying it up here

local new_BeeQeen_Spawn = State{
    name = "special_atk2_nr",
    tags = { "spawnguards", "busy", "nosleep", "nofreeze" },

    onenter = function(inst)
        local oldnum = inst.components.commander:GetNumSoldiers()
        local soldiers = inst.components.commander:GetAllSoldiers()
        local numbees = #soldiers
        if numbees >= 8 or inst.specialatk2 == false then
            inst.sg:GoToState("idle")
        else
        
        FaceTarget(inst)
        inst.components.locomotor:StopMoving()
        inst.AnimState:PlayAnimation("spawn")
        inst.SoundEmitter:PlaySound("dontstarve/creatures/together/bee_queen/spawn")
        end
    end,

    timeline =
    {
        TimeEvent(16 * FRAMES, function(inst)
            inst.sg.mem.wantstospawnguards = nil
            inst.components.timer:StartTimer("spawnguards_cd", inst.spawnguards_cd)

            local oldnum = inst.components.commander:GetNumSoldiers()
            local x, y, z = inst.Transform:GetWorldPosition()
            local rot = inst.Transform:GetRotation()
            local num = math.random(TUNING.BEEQUEEN_MIN_GUARDS_PER_SPAWN, TUNING.BEEQUEEN_MAX_GUARDS_PER_SPAWN)
            if num + oldnum > TUNING.BEEQUEEN_TOTAL_GUARDS then
                num = math.max(TUNING.BEEQUEEN_MIN_GUARDS_PER_SPAWN, TUNING.BEEQUEEN_TOTAL_GUARDS - oldnum)
            end
            local drot = 360 / num
            for i = 1, num do
                local minion = inst.components.petleash:SpawnPetAt(x, y, z, "beeguard")
                local angle = rot + i * drot
                local radius = minion.Physics:GetRadius()
                minion.Transform:SetRotation(angle)
                if inst.isshiny ~= 0 then
                    minion.isshiny = inst.isshiny
                    minion.AnimState:SetBuild("bee_guard_shiny_build_0"..minion.isshiny)
                end
                angle = -angle * DEGREES
                minion.Transform:SetPosition(x + radius * math.cos(angle), 0, z + radius * math.sin(angle))
                minion:OnSpawnedGuard(inst)
            end
            inst.specialatk2 = false
            if oldnum > 0 then
                local soldiers = inst.components.commander:GetAllSoldiers()
                num = #soldiers
                drot = 360 / num
                for i = 1, num do
                    local angle = -(rot + i * drot) * DEGREES
                    local xoffs = TUNING.BEEGUARD_GUARD_RANGE * math.cos(angle)
                    local zoffs = TUNING.BEEGUARD_GUARD_RANGE * math.sin(angle)
                    local mindistsq = math.huge
                    local closest = 1
                    for i2, v in ipairs(soldiers) do
                        local offset = v.components.knownlocations:GetLocation("queenoffset")
                        if offset ~= nil then
                            local distsq = distsq(xoffs, zoffs, offset.x, offset.z)
                            if distsq < mindistsq then
                                mindistsq = distsq
                                closest = i2
                            end
                        end
                    end
                    table.remove(soldiers, closest).components.knownlocations:RememberLocation("queenoffset", Vector3(xoffs, 0, zoffs), false)
                end
            end
        end),
        CommonHandlers.OnNoSleepTimeEvent(32 * FRAMES, function(inst)
            inst.sg:RemoveStateTag("busy")
            inst.sg:RemoveStateTag("nosleep")
            inst.sg:RemoveStateTag("nofreeze")
        end),
    },
    
    onexit = function(inst)
        inst:DoTaskInTime(10, function(inst) inst.specialatk2 = true end)
    end,
    
    events =
    {
        EventHandler("animqueueover", function(inst) inst.sg:GoToState("idle") end ),
    },
}

AddStategraphPostInit("SGbeequeenp", new_BeeQeen_Spawn)

This is a copy of the one of the states from the stategraph of the playable Bee Queen from Leonardo Coxington's Playable Pets: New Reign. I changed one line in the spawn function (and it works, I tested it in the actual mod stategraph). I don't know if this is the proper method for replacing a state post init, but this is what I found from searching. I don't mind figuring it out as I go, I am just stuck on this CommonHandlers issue. Thanks again for any help.

Edited by meltorefas
Link to comment
Share on other sites

Hmm have you tried deleteing:

CommonHandlers.OnNoSleepTimeEvent(32 * FRAMES, function(inst)

end),

Its my only guess as I can't find it in leonardo's mod (maybe its outdated?)  and I'm not finding similar lines of code that have commonhandlers in the modmain.

Link to comment
Share on other sites

CommonHandlers is in the stategraphs, not the modmains. Which I am guessing is the problem I am running into. The whole thing I am trying to do is replace a function within an existing stategraph, post init. So I am using an AddStategraphPostInit function from my mod's modmain to try and do that. I can't exactly delete the reference to CommonHandlers since it is needed for that state to work properly.

I found that CommonHandlers is declared inside stategraphs/commonstates.lua, and is used by a lot of creatures. I just can't figure out how to access it. I tried adding require("stategraphs/commonstates") to my modmain, which is what the NPC stategraphs have, but it didn't work. I tried creating using a local variable, SGCommon = require("stategraphs/commonstates"), and changing it to SGCommon.CommonHandlers, but that gives me an error "attempt to index local 'SGCommon' (a boolean value)".

In the meantime I figured out why inst:SetStategraph wasn't working... the playable pets characters have functions that reset their stategraphs (among other things) to their own locally stored values, thus overwriting any change I make. Contacting the mod author to see if he'll expose those local variables, heh. I would still love to know how to properly mod a stategraph post init to replace a state, though. Still can't find much on it.

Link to comment
Share on other sites

require "stategraphs/commonstates"
local CommonHandlers = GLOBAL.CommonHandlers

It will work only it this order. You need to use require because GLOBAL.CommonHadlers doesn't exist when the modmain is called (I think it will be defined by the game when the first stategraph will be initiated).

Anyway, it is not your main problem. You are trying to add a custom state, not to reinit existing:

AddStategraphPostInit("SGbeequeenp", new_BeeQeen_Spawn)

Must be used in another way. For example:
 

local function SGWilsonPostInit(sg)
	local _onenter = sg.states["run_start"].onenter
	sg.states["run_start"].onenter = function(inst)
		
		_onenter(inst)
		if not (inst.sg.statemem.riding or inst.sg.statemem.heavy)
		  and SlowEnoughSandWalk(inst) then
			inst.sg.statemem.sandstorm = true
			inst.AnimState:PushAnimation("sand_walk_pre")
		end
	end

	local _onenter2 = sg.states["run"].onenter
	sg.states["run"].onenter = function(inst)
		if not (inst.components.rider:IsRiding() or inst.components.inventory:IsHeavyLifting())
		  and SlowEnoughSandWalk(inst) then
			inst.components.locomotor:RunForward()
			if not inst.AnimState:IsCurrentAnimation("sand_walk") then
				inst.AnimState:PlayAnimation("sand_walk", true)
			end
			inst.sg:SetTimeout(inst.AnimState:GetCurrentAnimationLength())
		else
			_onenter2(inst)
		end
	end
end

AddStategraphPostInit("wilson", SGWilsonPostInit)

If you want to add a custom state, use 

AddStategraphState("stategraph_name", state_var)

 

Link to comment
Share on other sites

On 7/22/2018 at 3:58 AM, ksaab said:

require "stategraphs/commonstates"
local CommonHandlers = GLOBAL.CommonHandlers

It will work only it this order. You need to use require because GLOBAL.CommonHadlers doesn't exist when the modmain is called (I think it will be defined by the game when the first stategraph will be initiated).

This is good to know, thanks!

On 7/22/2018 at 3:58 AM, ksaab said:

Anyway, it is not your main problem. You are trying to add a custom state, not to reinit existing:

I don't think I understand what you mean, because I am actually trying to replace/overwrite an existing state, not add a new one; the specific state is called from elsewhere in the mod, and I wanted to overwrite one of the lines that would thus be executed. If there is some other/better way to do that, that's fine, but I am not sure I understand what you're saying otherwise, because adding a new state wouldn't help me change what happens when the existing state is called, would it?

Anyways, the mod author is letting me contribute to the code and also move the functions I want to change out of the stategraph and into the prefab, which will make things much easier (though redundant). I would still love to know how to actually replace a state, though.

Link to comment
Share on other sites

AddStategraphPostInit("stategraph_name", some_function)

When the stategraph will be initiated, the some_function will be called with stategraph itself as the first argument. It gives you an access to all state's variables:

local function some_function(sg) --sg is the statgraph "stategraph_name"
	local _onenter = sg.states["some_state"].onenter --saving old onenter function as a local var
	sg.states["some_state"].onenter = function(inst) --replacing it with a new one
		_onenter(inst) --call an original onenter if needed
		inst.SoundEmitter:PlaySound("some_sound") --new action for the state
	end
end

This code plays a sound when a creature comes to this state in addition to the original code.

Or you can completely replace state with this:

local function some_function(sg) --sg is the statgraph "stategraph_name"
      sg.states["some_state"] = State{ your state params }
      --or
      sg.states["some_state"] = state_var --if you've created a state before
end

Now look at your code:

AddStategraphPostInit("SGbeequeenp", new_BeeQeen_Spawn)

new_BeeQeen_Spawn is a state-type object (table generally). The game will try to call it as a function and... it will crash, because it's a table.

 

AddStategraphState("stategraph_name", state_var)

This adds a new state to the stategraph and the state_var is a state-type object. May be if you add a state with an existing name (state name, not a variable name), it will overwrite an original state. I'm not sure, but it should.

Edited by ksaab
  • Thanks 1
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...