Jump to content

Recommended Posts

So I've been messing around with stategraphs a lot, and in the Stategraph States, the timeline has TimeEvents that link code to frames in an animation

 

Heres a quick example of a simple state with timeline events. This one is for the "Give" action, like when you give food or armor to a pig

State{        name = "give",                onenter = function(inst)            inst.components.locomotor:Stop()            inst.AnimState:PlayAnimation("give")         end,                timeline =        {            TimeEvent(13*FRAMES, function(inst)                inst:PerformBufferedAction()            end),        },                events=        {            EventHandler("animover", function(inst) inst.sg:GoToState("idle") end ),        },    },   

And the timeline is pretty useful because it's like a DoTaskInTime that cancels itself if the animation is interrupted before that frame (frame 13 in the above example).

And same with the "animover" event, except that's a hassle to change.

 

So I've been using "inst.AnimState:Pause()" and "inst.AnimState:Resume()" to temporarily pause animations, and I assumed that it would also pause the TimeEvents. But it turns out that it doesn't pause the TimeEvents at all. They still happen during the frozen animation. Only the "animover" events are paused and resumed correctly.

 

So I'm pretty aweful when it comes to timed events. I can't even figure out how to cancel a DoPeriodicTask.

 

But is there anything I can do to pause and resume these stategraph timeevents on command? 

So I'm pretty aweful when it comes to timed events. I can't even figure out how to cancel a DoPeriodicTask.

I believe it's just:

-- task is the instance of the periodic tasktask:Cancel() 

You can get the task instance when creating it:

task = doer:DoPeriodicTask(...)
Edited by Blueberrys

To add to what BB said, you can definitely get the TimeEvent in question by navigating through the stategraph (if you don't make a new state, in which case you should just assign a variable).

 

By the way, the TimeEvent doesn't stop because it's essentially a TaskInTime, and FRAMES is 1/30 of a second (hardcoded I think, which would be bad practise).

 

I believe it's just:

-- task is the instance of the periodic tasktask:Cancel() 

You can get the task instance when creating it:

task = doer:DoPeriodicTask(...)

 

Hm, I can't get it to work.. I think I'm understanding it wrong though, Am I doing it right?

Is it supposed to look like this? (assuming I'm using it in a stategraph)

TimeEvent(10*FRAMES, function(inst)	inst.task = inst:DoPeriodicTask(0.1, function()		if inst:HasTag("rolling") then		--DO THE THING			inst:DoTaskInTime(3, function(inst)				inst.task:Cancel()			end)		end	end)end),--ALSO CAN IT BE CANCELED FROM ANOTHER THREAD LIKE THIS IF I WANTED TO?TimeEvent(20*FRAMES, function(inst)	--inst.task:Cancel()end),

I've tried a bunch of other things too but couldn't get it to work.

I'm probably setting it up wrong.

 

I once got "inst:CancelAllPendingTasks()" to work. But it also canceled the task that resumes the animation, so that didn't help

To add to what BB said, you can definitely get the TimeEvent in question by navigating through the stategraph (if you don't make a new state, in which case you should just assign a variable).

 

By the way, the TimeEvent doesn't stop because it's essentially a TaskInTime, and FRAMES is 1/30 of a second (hardcoded I think, which would be bad practise).

 

 

 

 

 

They're all custom states. What do you mean I could assign it a variable? What is this?  How is do things I can't.

I wish there was more documentation on time events. Does anyone know any game files with a lot of time tasks that I could study?

 

 

Theres a whole bunch of stuff in stategraphs.lua that I would love to try out, but have no idea how to apply them. This code looks like it might be able to freeze the current stategraphs, but I have no idea how to use it

function StateGraphWrangler:Hibernate(inst)    if self.instances[inst] then        self:SendToList(inst, self.hibernaters)    endendfunction StateGraphWrangler:Wake(inst)    if self.instances[inst] then       self:SendToList(inst, self.updaters)    endend
function StateGraphWrangler:Sleep(inst, time_to_wait)
    if self.instances[inst] then
        local sleep_ticks = time_to_wait/GetTickTime()
        if sleep_ticks == 0 then sleep_ticks = 1 end
 
        local target_tick = math.floor(GetTick() + sleep_ticks) + 1
        local waiters = self.tickwaiters[target_tick]
 
        if not waiters then
            waiters = {}
            self.tickwaiters[target_tick] = waiters
        end
        self:SendToList(inst, waiters)
    end
end

 

 

Is there a way to pause all tasks at once?

I know how to kill all tasks, but I want to be able to resume them later

 

Wait, no I guess I wouldnt want all tasks paused, because the function the resumes the tasks is also a TaskInTime. um.

 

 

Also, I think FRAMES is actually 1/60. For the longest time, I thought it was all 24 fps, until just recently I tested something out in stategraphs to see how many frames it prints out in 1 second and now I'm pretty sure it's 60

@pickleplayer I think the problem is with your references.

TimeEvent(10*FRAMES, function(inst)    -- Let's call this "inst" variable here "inst_1"    inst.task = inst:DoPeriodicTask(0.1, function()        if inst:HasTag("rolling") then         --DO THE THING             -- Inside the function below, a *new* inst variable is received as a            -- parameter, lets call it "inst_2"            inst:DoTaskInTime(3, function(inst)                -- If "inst_2" is not the SAME instance as "inst_1"                -- (or at least has the same data),                -- you won't have the task stored at inst_2.task                inst.task:Cancel()            end)        end    end) end), TimeEvent(20*FRAMES, function(inst)    -- If the "inst" here is the same instance as "inst_1", then yes.    -- But this function also has "inst" as a parameter.    --inst.task:Cancel()end),

To fix that, you could just change the name of the parameters so they don't interfere.

inst:DoTaskInTime(3, function(inst_2)-- ...end)

-

 

Edit: Also, check out scheduler.lua

Edited by Blueberrys

@pickleplayer I think the problem is with your references.

To fix that, you could just change the name of the parameters so they don't interfere.

inst:DoTaskInTime(3, function(inst_2)-- ...end)

-

 

Edit: Also, check out scheduler.lua

 

 

oh, just typed up a whole response before realizing the previous code actually DID work, and I had forgotten to disable the workaround I was using, so I didn't realize the task had been canceled. woops.

 

Anyways, Thanks! so now I know how to cancel time tasks, but is there a way to pause them? I've already combed through scheduler.lua, but I don't see anything that looks like it will work. There is a Sleep() function, but it's marked under "These are to be called from within a thread", And I was hoping to be able to pause them from elsewhere

 

Can the Stategraph time events even be paused? I tried to do something similar with them like the dopriodictask, but it just crashes.

Aww, well I am very dissapointed.

 

I finally managed to apply the stategraph wrangler functions from stategraph.lua in my code, only to find out they don't work the way I was hoping they would.

I added this to the modmain (because I was having problems editing the file directly)

AddGlobalClassPostConstruct("entityscript", "EntityScript", function(self)	function self:FreezeStateGraph()		if self.sg then			GLOBAL.SGManager:Sleep(self.sg, 8*(1/30))		end	end		function self:PauseStateGraph()		if self.sg then			GLOBAL.SGManager:Hibernate(self.sg)		end	end		function self:UnPauseStateGraph()		if self.sg then			GLOBAL.SGManager:Wake(self.sg)		end	endend)

I tried two methods, the "sleep" method, and the "hibernate/wake" method. (Although they both worked exactly the same way)

 

Upon hibernating the stategraphs, it succesfully stopped the TimeEvents from happening while in hibernation. But as soon as it came out of hibernation, all of the time events that would have gone off during its hibernation just all went off at once, and it resumed as if no pause ever happened. :(

 

Guess I'll have to try something else. I'm looking at update.lua next because theres this important looking code snippet in the main update function

for i = last_tick_seen + 1, tick do            TheSim:ProfilerPush("LuaSG")            SGManager:Update(i)            TheSim:ProfilerPop()                        TheSim:ProfilerPush("LuaBrain")            BrainManager:Update(i)            TheSim:ProfilerPop()        end

lines 2 and 3 specifically are what I want to mess with, IF I can figure out how to actually edit it.

 

also, where is the source of this "TheSim" class?? I can't actually find it in the game files, and it's not simutil.lua

 

 

(also, oops, it is in 30 fps, my bad mobbsar)

TheSim is constructed in C++, I think, just like AnimState, Transform, etc.

 

Does that mean it isn't in the game files at all?

 

Is there maybe some documentation of the content of those classes? Like the global functions on pastebin? http://pastebin.com/D4c4SRaD

I DID IT

oh my god. I actually fixed hitlag.

 

Even though I doubt anyone else will ever need to know how to do this, I'm going to leave this here anyways because you never know if anyone down the road will want to know how to pause a stategraph time event for their mod.

 

Okay so my old broken method was some rediculously over-complicated system changing how update.lua worked so that stategraphs were updated on a seperate timeline from the rest of the game that was x frames slower, where x was the number of hitlag frames, and once a state was over, it would catch up to the real game time to prevent future state events from happening x frames slower than they should.

And it actually kind of worked at first. Except that SGmanager applies to ALL states, and for BOTH players, so if one player changed states after being hit while the other was still in their attack animation, it would mess up their timeline and they would be frozen in that state permanently.

 

SO I cut all that out finally figured out a way to freeze individual stategraphs from anywhere.

 

at the end of hitlag, I made this function that adjusts the state's current time so that it's exactly where it left off when the hitlag started.

if self.inst.sg then --PROJECTILES DON'T HAVE STATEGRAPHS SO THINGS GET WEIRD IF YOU TRY TO UPDATE THEM	self.inst.sg.timeinstate = self.inst.sg.timeinstate - ((self.hitlag-3)*FRAMES) -- -3 FOR THE 3 FRAME STARTUP DELAYend

(it's strange how some stategraph variables are measured in frames and some are measured in seconds)

 

and then to prevent any TimeEvents from going off during the duration of hitlag before the timeinstate was reset, I added these two entityscript functions

AddGlobalClassPostConstruct("entityscript", "EntityScript", function(self)	function self:PauseStateGraph()		if self.sg then			GLOBAL.SGManager:Hibernate(self.sg)		end	end		function self:UnPauseStateGraph()		if self.sg then			GLOBAL.SGManager:Wake(self.sg)		end	endend)

Which are called at the beginning and end of hitlag respectively.

self.inst:PauseStateGraph()--AND THEN LATER ONself.inst:UnPauseStateGraph() 

They were added through the modmain because for some reason, any changes applied to major game files like entityscript.lua don't take effect at all.

 

So there you go. And it only took me a few months.

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
×
  • Create New...