1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. The 15th Mini-Mapping Contest came to an end. The Secrets of Warcraft 3 are soon to be revealed! Come and vote in the public poll for your favorite maps.
    Dismiss Notice
  3. The 12th incarnation of the Music Contest is LIVE! The theme is Synthwave. Knight Rider needs a song to listen to on his journey. You should definitely have some fun with this theme!
    Dismiss Notice
  4. Join other hivers in a friendly concept-art contest. The contestants have to create a genie coming out of its container. We wish you the best of luck!
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJass] SleepAction - A Cinematic Lifesaver

Discussion in 'JASS/AI Scripts Tutorials' started by Bribe, Aug 25, 2010.

  1. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,940
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    When I was making cinematic scripts a few months ago, I was disappointed by the inaccuracies of
    TriggerSleepAction()
    , so I moved to fix that problem with Waldbaer's Exact-Timing Cinematic tutorial. Unfortunately, that looked like a mess and was getting tiresome to create the trigger queues for every wait period that I wanted to be exact.

    I moved to JASS.

    What happened then was the precedence-behavior of programming messed everything up in a whole new way. The
    TimerStart()
    call only compiles functions above it, which made my cinematic script look out-of-order and I was finding it hard to expand on it and to locate a specific section (because everything was in reverse-chronological order).

    I tried learning Anitarf's Cinematic System but I found myself drifting further and further away from the approach I wanted to take with creating the cinematic. I wanted to have a script that looked like it was mine and would be easy for someone to pick up on.

    The SleepAction library is the realization of those dreams I had. Now, thanks to a textmacro, it is an easy thing to implement a wait period. The only parts that require thinking are in the parameters passed to the macro.

    Example 1:

    Code (vJASS):
    scope foo initializer bar // requires SleepAction
     
        private function bar takes nothing returns nothing
            //! runtextmacro SleepAction("5.5", "1")
            call BJDebugMsg("Hello,") // Executed exactly 5 and a half seconds into the game.
            //! runtextmacro SleepAction("0.1", "A")
            call BJDebugMsg("World!") // Executed exactly 1/10th of a second after the last message.
        endfunction
     
    endscope
     


    The first of the parameters is the period you want to wait, which must be a
    real
    value or variable. Everything below the line of the textmacro will not execute until the *exact moment* the wait period has ended. Not slightly before or slightly after which you get from
    TriggerSleepAction()
    and without disorganizing and/or complicating your script to accomplish something which should not have ever been a complicated process.

    The textmacro must be within a
    scope or library
    (pretty much every vJass user does this anyway) and within a vJass
    function
    (not a
    method
    or a Zinc function [Zinc users have anonymous functions to make up for all the features Zinc didn't get]). Note that
    local
    variables are not remembered after the textmacro line, though it is possible to declare new local variables after the line. How it works:

    Invisible to you while scripting, the textmacro breaks the function in two. A
    timer
    gets started with the duration you specified and then, using a vJass
    function interface
    combined with TimerData from Vexorian's TimerUtils, the "second part" of the function is executed when the timer expires. For this reason, the second parameter must be a scope-unique sequence of letters and/or numbers and/or underscores as that parameter more or less gives the hidden function a name.

    I find it user-friendly to just name each part a sequencial number (1,2,3...) as you scroll down. The hidden function is a
    private function
    which is prefixed with SleepAction_ and, being
    private
    , prefixed with the scope's name as well, with the second parameter's name tacked on at the end of it all.

    Hopefully, this script allows you to create cinematics in an environment which is suitable for you!

    Example 2:

    Code (vJASS):

       
        scope Scene1 initializer Init // requires SleepAction
           
            private function Message takes real duration, string msg returns nothing
                call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, duration, "|cff888888" + msg + "|r")
            endfunction
           
            private function Init takes nothing returns nothing
           
                //! runtextmacro SleepAction("0.0", "1")
                // Actions below this line happen exactly 0 seconds into the game.
                call Message(5.0, "This is an easy method to create cinematics.")
               
                //! runtextmacro SleepAction("5.0", "2")
                // Actions below this line happen exactly 5 seconds after the last message.
                call Message(7.0, "Using this tool, you can create perfect wait-periods without " + /*
                                */
    "distorting the look and feel of your cinematic scripts.")
                           
                //! runtextmacro SleepAction("7.0", "3")
                // Actions below this line happen exactly 7 seconds after the last message.
                call Message(10.0, "All you have to do is fill in the blanks:\n" + /*
                                 */
    "//! runtextmacro SleepAction(\"<Real Duration>\", \"<Scope-Unique Name>\")")
               
                call TriggerSleepAction(10.0)
                // Yes, you can safely mix regular TriggerSleepActions in, if you want.
            endfunction
           
        endscope
       
     


    Cons



    I can't just butter this whole thing up. There are some real things to consider and to review from what I mentioned already.

    A) The textmacro may only be enclosed in a function which
    takes nothing returns nothing
    .
    B) The function may not be a method and may not be in the Zinc language.
    C) The function must be contained in a scope or a library.
    D) Local variables are forgotten after the textmacro line.
    E) The textmacro line may not be embedded within a block such as
    if/then/else/endif
    or
    loop/endloop
    .
    F) You must input a scope-unique name as a second textmacro parameter.

    SleepAction script:

    Code (vJASS):

    library SleepAction requires TimerUtils
       
        function interface SleepActionFunc takes nothing returns nothing
       
        globals
            private timer t = null
        endglobals
       
        private function Sleep takes nothing returns nothing
            set t = GetExpiredTimer()
            call SleepActionFunc(GetTimerData(t)).execute()
            call ReleaseTimer(t)
        endfunction
       
        function DoSleepAction takes SleepActionFunc func, real duration returns nothing
            set t = NewTimer()
            call SetTimerData(t, integer(func))
            call TimerStart(t, duration, false, function Sleep)
        endfunction
       
        //! textmacro SleepAction takes RealSleepDuration, FuncName
                globals
                    private keyword SleepAction_$FuncName$X
                endglobals
                call DoSleepAction(SleepAction_$FuncName$X, $RealSleepDuration$)
            endfunction
           
            private function SleepAction_$FuncName$X takes nothing returns nothing
        //! endtextmacro

    endlibrary
     
     
    Last edited: Feb 7, 2011
  2. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,427
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Cool technique. Although a lot may prefer to do this themselves, I'm sure this will be useful to people who want to get cinematics done fast. =D

    Good job, I'll detach your signature from the post.

    ~Approved. :goblin_good_job:
     
  3. SanKakU

    SanKakU

    Joined:
    May 11, 2008
    Messages:
    1,183
    Resources:
    1
    Maps:
    1
    Resources:
    1
    in the middle of reviewing your code. from your explanation of the system, it looks good so far. still reviewing but it looks very promising.

    by the way i noticed an error in example 2. check the first line.

    also, do you know if this system is good for things besides cinematics?

    i remembered when first learning this coding stuff being frustrated with timers incredibly and even now i still don't understand them entirely, even though i use them frequently. this system kind of looks like i wonder why it wasn't created sooner, if indeed it works with things besides cinematics, that is.

    by the way, i think it's cool that you're using textmacro to make function name.

    edit:
    how are you making cinematics? can you give a bigger example? so far every time i try to use this i get errors. maybe there are some bugs, or i just don't know what i'm doing.

    the problem i keep running into is variables keep becoming undeclared.
     
    Last edited: Feb 8, 2011
  4. ZEEP

    ZEEP

    Joined:
    Feb 27, 2011
    Messages:
    17
    Resources:
    0
    Resources:
    0
    A) The textmacro may only be enclosed in a function which takes nothing returns nothing .
    D) Local variables are forgotten after the textmacro line.


    Even worse than TimerStart but as you said for your cinematics may be usefull.
     
  5. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,940
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    I'm planning to update the script to this:

    Code (vJASS):

    library SleepAction requires TimerUtils, Table
       
        globals
            private timer t = null
            private key theKey
            private Table s = theKey
        endglobals
       
        private function Sleep takes nothing returns nothing
            local integer id
            set t = GetExpiredTimer()
            set id = GetHandleId(t)
            call ExecuteFunc(s.string[id])
            call s.string.remove(id)
            call ReleaseTimer(t)
        endfunction
       
        function DoSleepAction takes string func, real duration returns nothing
            set t = NewTimer()
            set s.string[GetHandleId(t)] = func
            call TimerStart(t, duration, false, function Sleep)
        endfunction
       
        //! textmacro SleepAction takes RealSleepDuration, FuncName
                call DoSleepAction(SCOPE_PRIVATE + "SleepAction_$FuncName$X", $RealSleepDuration$)
            endfunction
           
            private function SleepAction_$FuncName$X takes nothing returns nothing
        //! endtextmacro

    endlibrary

     



    For straightforward cinematics in GUIJASS (I intend to use this trick to easily patch up my original WarChasers map):

    • Events
    • Conditions
    • Actions
      • Custom script: endfunction
      • Custom script: function DoAction takes nothing returns nothing
      • Custom script: call ExecuteFunc(udg_globalString)
      • Custom script: endfunction
      • Custom script: function ActionTime takes nothing returns nothing
      • Custom script: call TimerStart(udg_globalTimer, udg_globalReal, false, function DoAction)
      • Custom script: endfunction
      • -------- The above functions could also go into a map header
      • Custom script: function Phase1 takes nothing returns nothing
      • Set globalString = Phase2
      • Set globalReal = 3.00
      • Custom script: call ActionTime()
      • Custom script: endfunction
      • Custom script: function Phase2 takes nothing returns nothing
      • Set globalString = Phase3
      • Set globalReal = 6.00
      • Custom script: call ActionTime()
      • Custom script: endfunction
      • Custom script: function Phase3 takes nothing returns nothing
     
    Last edited: Jul 28, 2015
  6. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,786
    Resources:
    11
    Models:
    4
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    11
    For cinematic needs, why not just create a single periodic timer and increment a count variable by one for each iteration, then use the count as the actual time-frame to refer to with a simple switch-case structure?

    --> everything is ordered top-down
    --> simple easily understandable code
    --> almost no custom script needed, even for GUIers
    --> 100% accurate

    Obviously, you can't use locals here either, but in cinematics, why even use locals anyway?
     
  7. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,940
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    I think I know what you mean. Like expiring once every.25 seconds calling a single function which increments a timestamp variable, and in that same function have an if/else structure to identify which phase to execute. I like that approach - thanks for sharing!
     
  8. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,786
    Resources:
    11
    Models:
    4
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    11
    It's basicly my go-to solution for creating script events of any kind where efficiency doesn't really matter.
    The only downside is that it's hard to adjust timings afterwards, as you need to adjust all following timings aswell.

    You can solve that easily by not having a periodic timer. Instead, make the timer function recursive:

    Code (vJASS):
    globals
        private integer count = 0
        private timer t = CreateTimer()
    endglobals

    private function callback takes nothing returns nothing
        local real wait = 0
        set count = count + 1

        if count == 1 then
            //here be code
            set wait = 5
        elseif count == 2 then
            //here be code
            set wait = 10
        elseif count == 3 then
            //here be code
            set wait = 3
        endif

        call TimerStart(t, wait, false, function callback)
    endfunction

    function StartCinematic takes nothing returns nothing
        call callback()
    endfunction


    ... when you think about it: you could actually create textmacros for that to mimic the TriggerSleepAction syntax...


    And here is why this approach is so cool:

    Code (vJASS):
    globals
        private constant integer LAST_PHASE = 4

        private integer count = 0
        private timer t = CreateTimer()

        private boolean skipped = false
    endglobals

    private function callback takes nothing returns nothing
        local real wait = 0
        set count = count + 1

        if skipped then
            set count = LAST_PHASE
        endif
        if count == 1 then
            //here be code
            set wait = 5
        elseif count == 2 then
            //here be code
            set wait = 10
        elseif count == 3 then
            //here be code
            set wait = 3

        //...

        elseif count == LAST_PHASE then
            //the final state of your cinematic... remove cinematic mode, remove units etc.
        endif

        if not skipped then
            call TimerStart(t, wait, false, function callback)
        endif
    endfunction

    function StartCinematic takes nothing returns nothing
        call callback()
    endfunction
     
  9. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,940
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Perfect. I'm gonna do that for my map. That's a lot more sane.
     
  10. edo494

    edo494

    Joined:
    Apr 16, 2012
    Messages:
    3,855
    Resources:
    5
    Spells:
    1
    JASS:
    4
    Resources:
    5
    and if you want extra performance juice, do binary search on the numbers :D