• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Old Unit Transmission

Level 24
Joined
Jun 26, 2020
Messages
1,853
JASS version
vJASS:
library Transmission /* version 2.2.0
  
    */requires /*  
    */TimerUtils, /*
    */Alloc, /*
    */ArrayList, /*
    */optional StoreUnitColor
  
    //! novjass
    //How it works:
    //    Set a value of type Transmission and use the function create like this:
  
            local Transmission curr = Transmission.create()
            // or
            local Transmission curr = Transmission.CreateEx(force toForce, integer data)
          
    //    also you can set the target force of the transmission using:
          
            call curr.SetTargetForce(force toForce) //This not assing a force, just move the players of the input to the actually target force, so using a function that creates a force
                                                    //in the argument toForce will cause an object leak.
          
    //    you can also save an integer to use during the transmission using:
  
            call curr.Data = data /*To recover the data use:*/ curr.Data
          
    //    if you wanna add or remove a player or a force to the target force just use the normal functions ForceAddPlayer, ForceRemovePlayer and the added ForceAddForce, ForceRemoveForce
      
    //    To reduce the process use:
  
            local Transmission curr = Transmission.CreateEx(toForce,data)
      
     //   Then to add a dialogue line just do:
      
            call curr.AddLine(unit whichUnit, playercolor color, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait)
          
          "whichUnit": The unit who do the dialogue line.
          "color": The color of the unit in the dialogue (if you set this value to null so then, if you have the StoreUnitColor library the unit will have their actual color, if not, it will have their owner color).
          "unitName": The name of that unit.
          "soundHandle": The sound that will be played during the dialogue line.
          "message": what the unit will say.
          "timeType": Is like the GUI function, you can use 0 (Transmission.ADD), 1 (Transmission.SET), 2 (Transmission.SUB), another value will make the wait time to 0.
          "timeVal": Is like the GUI function, the normal duration of the transmission is the duration of the sound and you can edit the value depending of this and the previous value.
          "wait": Is like the GUI function, if this is true the next transmission will be wait the time depending of the previous two values.
      
    /*    In case you don't wanna set a unit, but instead a unit-type you can use */curr.AddLinebyId/*instead (default color BLACK)*/
  
    //    Also you can add actions between the lines using the function:
      
            call curr.AddAction(code actions, real delay)
          
          "actions": The function that will run.
          "delay": If you wanna add a delay to the next action or line.
        
    //    If you wanna the action run in the exact moment a line is happening just set the delay to 0.00 and put it right before the line.
    /*    If you wanna just add an extra delay just put */null/* in the actions part*/
      
    //    I have to say this steps are assinged an index by the order they were declared that is the number of the step.
  
    //    You can also add an action to the end of the transmission (this function will run yes or yes even you skip the transmission)  using:
  
            call curr.AddEnd(code func)
          
    /*    Where you can use the values */set curr=Transmission.instance/*to reffer to the actual instance, and with that use */curr.Data/* if you stored one and */curr.Skipped/* to detect
          if the cinematic was skipped but if was skipped in the last line this value will remain false.*/
          
    //    After set the lines call the function
  
            call curr.Start()
          
    //    To start the transmission, and to skip the transmission (the end function will run anyway) just press the ESC button,
    //    (this will skip all the transmissions where the player is) but if the implied force has more than one player the process will continue
    //    to the rest of players on that force (.toForce is empty). The transmission will end only if all the implied players skip the transmission (So if you use "All players"
    /*    probably you never won't skip the transmission) but you can use the */function Force(player p)/* to have a force just for 1 player (Don't destroy those forces).*/
  
    //    If you wanna you can also pause the transmission using:
          
            call curr.Stop(boolean fadeout) //For the actual played sound.
          
    //    or:
  
            call curr.StopUntil(boolean fadeout, real delay) //If you wanna the transmission resume itself after a while
          
    //    And to resume manually use:
  
            call curr.Resume()
          
    //    Note: The transsmision and its elements are not stored after start it, they are recycled once the transmission ends.
  
    //    Other members:
  
            force toForce, to reffer the force to which it is addressed the transmission.
            force OriginalTargetForce, if you edited the force toForce, you can have the original force with this.
            player toPlayer, if the transmission is just for one player you can use this instead, in other case this will return the last added player. //(I don't know the order)
            integer LineNumber, is the number of the line  that is running.
            integer ActionNumber, is the number of the action that is running.
            integer StepNumber, is the number of the step that is running (line or actions).
            boolean InUse, returns if the instance is allocated.
            boolean Paused, returns if the transmission is paused.
            TransmissionElements Elements, to reffer to the elements instance of the actual step.
          
            method AddSceneActions takes code func returns nothing
                /*If you wanna add an action that runs everytime an step (line or action except the end) is runned, this also can be a replace of .AddAction in some
                moments if you compare the number of the step with StepNumber, to know if is a line or action use the member boolean IsLine
                and if is true you can use the member integer LineNumber to know the number of the actual line.
                This also runs before the action or line is runned, so you can use it to edit the current step.*/
              
            method RemoveSceneActions takes triggeraction actions returns nothing
                //This remove the actions that you added before.
              
            //If you wanna set the elements by separated you can use the member
          
            method AddStep takes string whattype returns TransmissionElements
                /*This add an step that also have an index by the order is placed, the input only can be*/"line"/* and */"action"
              
            method InsertStep takes integer stepnumber, string whattype returns TransmissionElements
                /*This add an step but the index is set manually, if there are steps that were set after this and after the transmission was started
                (including that was had that stepnumber) those index will be increased by 1 to not overwrite.*/
          
            method RemoveStep takes integer stepnumber returns nothing
                //This remove an step using the number of the step, in this case the stepnumber of the next steps will be decreased by 1.
          
            //After add the step the returned value have their own members
          
            unit Unit //This is the unit who will be marked during the transmission (not who will apear in the transmission).
            integer UnitType
            playercolor Color
            string Name
            sound Sound
            string Message
            real Time
            integer TimeType
            boolean WillWait
            boolean Delay
          
            integer NumberLine  //Please don't edit this value
            integer NumberAction  //Please don't edit this value
            integer NumberStep  //Please don't edit this value
          
            method SetActions takes code func returns nothing  //This replace all the actions
            method AddActions takes code func returns nothing  //This add actions without affecting the added actions
            method GetActions takes nothing returns trigger
          
            //But if you wanna use this members using the number of the step use their "ByIndex" methods (these are members of Transmission not of TransmissionElements)
            //You have to do:
          
                call curr.Set$Name of the member$ByIndex(integer stepnumber, $type of the member$ value)
              
                set value = curr.Get$Name of the member$ByIndex(integer stepnumber) (Except WillWait and AddActions)
              
            //Example:
              
                local Transmission curr = Transmission.create()
                call curr.AddStep("line") //is the step 1
                call curr.AddStep("action") //is the step 2
                call curr.SetUnitByIndex(1,<your unit>)
                call curr.SetActionsByIndex(2,<your actions>)
              
                //A replace of:
              
                local Transmission curr = Transmission.create()
                local TransmissionElements what
                set what=curr.AddStep("line")
                call what.SetUnit(<your unit>)
                set what=curr.AddStep("action")
                call what.SetActions(<your actions>)
              
            // Here are more members:
          
            IntegerList Lines
            IntegerList Actions
            IntegerList Steps
            integer LineNumber  // Same as Transmission.instance.Elements.NumberLine
            integer ActionNumber  // Same as Transmission.instance.Elements.NumberAction
            integer StepNumber  // Same as Transmission.instance.Elements.NumberStep
            boolean IsLine
            boolean InUse // If is allocated
            boolean Paused
              
            // But if you wanna save the number of lines you can set default values (before than create the steps)
          
            unit DefUnit
            integer DefUnitType
            playercolor DefColor
            string DefName
            sound DefSound
            string DefMessage
            real DefTime
            integer DefTimeType
            boolean DefWillWait
            boolean DefDelay
              
    //! endnovjass
  
    //A function to use
    globals
        private IntegerList array AllInstances
        private integer Temp
        private force array What_Force
        private force Other
    endglobals
  
    private function Enum takes nothing returns nothing
        set Temp = Temp + 1
    endfunction
  
    function IsForceEmpty takes force f returns boolean
        if f == null then
            return true
        else
            set Temp = 0
            call ForForce(f,function Enum)
        endif
        return Temp == 0
    endfunction
  
    private function ForceAddForceEnum takes nothing returns nothing
        call ForceAddPlayer(Other, GetEnumPlayer())
    endfunction
  
    function ForceAddForce takes force principal, force other returns nothing
        if principal == null or other == null then
            return
        else
            set Other = other
            call ForForce(principal, function ForceAddForceEnum)
        endif
    endfunction
  
    private function ForceRemoveForceEnum takes nothing returns nothing
        call ForceRemovePlayer(Other, GetEnumPlayer())
    endfunction
  
    function ForceRemoveForce takes force principal, force other returns nothing
        if principal == null or other == null then
            return
        else
            set Other = other
            call ForForce(principal,function ForceRemoveForceEnum)
        endif
    endfunction
  
    function Force takes player p returns force
        return What_Force[GetPlayerId(p)]
    endfunction
  
    // This are the elements of each line transmission
  
    struct TransmissionElements extends array
  
        implement Alloc
        boolean isline
        private unit talker
        private integer talkerid
        private playercolor color
        private string name
        private real duration
        private integer timetype
        private boolean wait
        private sound mysound
        private string text
        private trigger actions
        integer NumberLine
        integer NumberAction
        integer NumberStep
      
        method destroy takes nothing returns nothing
            set this.talker = null
            set this.name = null
            set this.mysound = null
            set this.text = null
            set this.timetype = -1
            set this.duration = 0.00
            set this.wait = false
            set this.isline = false
            if this.actions != null then
                call TriggerClearActions(this.actions)
                call DestroyTrigger(this.actions)
                set this.actions = null
            endif
            set this.NumberLine = 0
            set this.NumberAction = 0
            set this.NumberStep = 0
            call this.deallocate()
        endmethod
      
        // To set the values
      
        method operator Unit= takes unit whichUnit returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Unit=|r")
                return
            endif
            set this.talker = whichUnit
        endmethod
      
        method operator UnitType= takes integer id returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000UnitType=|r")
                return
            endif
            set this.talkerid = id
        endmethod
      
        method operator Color= takes playercolor whichColor returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Color=|r")
                return
            endif
            set this.color = whichColor
        endmethod
      
        method operator Name= takes string unitName returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Name=|r")
                return
            endif
            set this.name = unitName
        endmethod
      
        method operator Sound= takes sound soundHandle returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Sound=|r")
                return
            endif
            set this.mysound = soundHandle
        endmethod
      
        method operator Message= takes  string message returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Message=|r")
                return
            endif
            set this.text = message
        endmethod
      
        method operator TimeType= takes integer timeType returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000TimeType=|r")
                return
            endif
            set this.timetype = timeType
        endmethod
      
        method operator Time= takes real timeVal returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Time=|r")
                return
            endif
            set this.duration = timeVal
        endmethod
      
        method operator WillWait= takes boolean wait returns nothing
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000WillWait=|r")
                return
            endif
            set this.wait = wait
        endmethod
      
        method operator Delay= takes real delay returns nothing
            if this.isline then
                debug call BJDebugMsg("This is a line |cffff0000Delay=|r")
                return
            endif
            set this.duration = delay
        endmethod
      
        method SetActions takes code func returns nothing
            if this.isline then
                debug call BJDebugMsg("This is a line |cffff0000SetActions|r")
                return
            endif
            if this.actions == null then
                set this.actions = CreateTrigger()
            else
                call TriggerClearActions(this.actions)
            endif
            if func != null then
                call TriggerAddAction(this.actions,func)
            endif
        endmethod
      
        method AddActions takes code func returns nothing
            if this.isline then
                debug call BJDebugMsg("This is a line |cffff0000AddActions|r")
                return
            //Only add actions if func is not null and this.actions was created previously
            elseif func == null then
                return
            elseif this.actions == null then
                debug call BJDebugMsg("Why do you wanna \"add\" actions if there isn't even a trigger with actions?")
                return
            else
                call TriggerAddAction(this.actions,func)
            endif
        endmethod
      
        //To get the values
      
        method operator Unit takes nothing returns unit
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Unit|r")
                return null
            endif
            return this.talker
        endmethod
      
        method operator UnitType takes nothing returns integer
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000UnitType|r")
                return 0
            endif
            return this.talkerid
        endmethod
      
        method operator Color takes nothing returns playercolor
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Color|r")
                return null
            endif
            return this.color
        endmethod
      
        method operator Name takes nothing returns string
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Name|r")
                return null
            endif
            return this.name
        endmethod
      
        method operator Sound takes nothing returns sound
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Sound|r")
                return null
            endif
            return this.mysound
        endmethod
      
        method operator Message takes nothing returns string
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Message|r")
                return null
            endif
            return this.text
        endmethod
      
        method operator TimeType takes nothing returns integer
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000TimeType|r")
                return 0
            endif
            return this.timetype
        endmethod
      
        method operator Time takes nothing returns real
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000Time|r")
                return 0.00
            endif
            return this.duration
        endmethod
      
        method operator WillWait takes nothing returns boolean
            if not this.isline then
                debug call BJDebugMsg("This is an action |cffff0000WillWait|r")
                return false
            endif
            return this.wait
        endmethod
      
        method operator Delay takes nothing returns real
            if this.isline then
                debug call BJDebugMsg("This is a line |cffff0000Delay|r")
                return 0.00
            endif
            return this.duration
        endmethod
      
        method GetActions takes nothing returns trigger
            if this.isline then
                debug call BJDebugMsg("This is a line |cffff0000GetActions|r")
                return null
            endif
            return this.actions
        endmethod
      
        // The methods that create the elements
      
        static method create takes string whattype returns thistype
            local thistype this = thistype.allocate()
          
            if whattype == "line" then
                set this.isline = true
            elseif whattype == "action" then
                set this.isline = false
            else
                debug call BJDebugMsg("Invalid type.")
                call this.deallocate()
                return 0
            endif
          
            return this
        endmethod
      
        static method create_action takes code func, real delay returns thistype
            local thistype this = thistype.create("action")
          
            // Since I can't store codes I use triggers
            if func!=null then
                set this.actions = CreateTrigger()
                call TriggerAddAction(this.actions,func)
            endif
            set this.duration = delay
          
            return this
        endmethod
      
        static method create_line takes unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait returns thistype
            local thistype this = thistype.create("line")
          
            set this.talker = whichUnit
            set this.name = unitName
            set this.mysound = soundHandle
            set this.text = message
            set this.timetype = timeType
            set this.duration = timeVal
            set this.wait = wait
            set this.isline = true
            if whichUnit == null then
                set this.talkerid = 0
                if whichColor == null then
                    static if LIBRARY_StoreUnitColor then
                        set this.color = PLAYER_COLOR_BLACK
                    else
                        set this.color = ConvertPlayerColor(PLAYER_NEUTRAL_AGGRESSIVE)
                    endif
                else
                    set this.color = whichColor
                endif
            else
                set this.talkerid = GetUnitTypeId(whichUnit)
                if whichColor == null then
                    static if LIBRARY_StoreUnitColor then
                        set this.color = GetUnitColor(whichUnit)
                    else
                        set this.color = GetPlayerColor(GetOwningPlayer(whichUnit))
                    endif
                else
                    set this.color = whichColor
                endif
            endif
          
            return this
        endmethod
      
    endstruct
  
    /*****************************************************/
    /********The actual struct of the transmission********/
    /*****************************************************/
  
    private module M
      
        // These are to make it more elegant than just put integers, but you can use the integers if you want
        static constant integer ADD = bj_TIMETYPE_ADD  // 0
        static constant integer SET = bj_TIMETYPE_SET  // 1
        static constant integer SUB = bj_TIMETYPE_SUB  // 2
      
        readonly static player LocalPlayer
        private static integer array Index
        private static integer array Index_current
      
        readonly static thistype instance
      
        readonly player toPlayer
        readonly force toForce
        readonly force OriginalTargetForce
        readonly IntegerList Lines
        readonly IntegerList Actions
        readonly IntegerList Steps
      
        unit DefUnit
        integer DefUnitType
        playercolor DefColor
        string DefName
        sound DefSound
        string DefMessage
        real DefTime
        integer DefTimeType
        boolean DefWillWait
        real DefDelay
      
        private boolean allocated
        private integer data
        private boolean paused
        private boolean skipped
        private sound played
        private trigger final
        private timer t
        private TransmissionElements elements
        private integer current
        private integer actual_line
        private integer actual_action
        private integer actual_step
        private boolean ended
        private trigger scene
      
        private method Invalid takes string func returns boolean
            local boolean b = this<=0 or not this.allocated
            static if DEBUG_MODE then
                if b then
                    call BJDebugMsg("Invalid instance |cffff0000"+func+"|r")
                endif
            endif
            return b
        endmethod
      
        // I use operators to display the error message everytime this values are called
      
        method operator LineNumber takes nothing returns integer
            call this.Invalid("LineNumber")
            if not this.elements.isline then
                debug call BJDebugMsg("This step is an action |cffff0000LineNumber|r")
                return 0
            endif
            return this.elements.NumberLine
        endmethod
      
        method operator ActionNumber takes nothing returns integer
            call this.Invalid("ActionNumber")
            if this.elements.isline then
                debug call BJDebugMsg("This step is an line |cffff0000ActionNumber|r")
                return 0
            endif
            return this.elements.NumberAction
        endmethod
      
        method operator StepNumber takes nothing returns integer
            call this.Invalid("StepNumber")
            if this.ended then
                return this.actual_step+1
            endif
            return this.elements.NumberStep
        endmethod
      
        method operator IsLine takes nothing returns boolean
            return this.elements.isline
        endmethod
      
        method operator InUse takes nothing returns boolean
            call this.Invalid("InUse")
            return this.allocated
        endmethod
      
        method operator Data= takes integer data returns nothing
            if this.Invalid("Data") then
                return
            endif
            set this.data = data
        endmethod
      
        method operator Data takes nothing returns integer
            call this.Invalid("Data")
            return this.data
        endmethod
      
        method operator Paused takes nothing returns boolean
            call this.Invalid("Paused")
            return this.paused
        endmethod
      
        method operator Skipped takes nothing returns boolean
            call this.Invalid("Skipped")
            return this.skipped
        endmethod
      
        method operator Elements takes nothing returns TransmissionElements
            call this.Invalid("Elements")
            return this.elements
        endmethod
      
        // When the transmission ends
      
        private static method ClearData takes nothing returns nothing
            local integer i = GetPlayerId(GetEnumPlayer())
            local integer j = 1
            loop
                exitwhen j > AllInstances[i].length
                if thistype.instance == AllInstances[i][j] then
                    call AllInstances[i].removeFrom(j)
                    exitwhen true
                endif
                set j = j + 1
            endloop
        endmethod
      
        private method finish takes nothing returns nothing
            call this.Steps.destroy()
            call this.Lines.destroy()
            call this.Actions.destroy()
            call ReleaseTimer(this.t)
            set this.ended = true
            set thistype.instance = this
            if this.final != null then
                call TriggerExecute(this.final)
                call TriggerClearActions(this.final)
                call DestroyTrigger(this.final)
                set this.final = null
            endif
            call ForForce(this.toForce, function thistype.ClearData)
            set thistype.instance = 0
            call DestroyForce(this.OriginalTargetForce)
            call DestroyForce(this.toForce)
            call TriggerClearActions(this.scene)
            call DestroyTrigger(this.scene)
            set this.elements = 0
            set this.scene = null
            set this.data = 0
            set this.played = null
            set this.toForce = null
            set this.toPlayer = null
            set this.t = null
            set this.allocated = false
            call this.deallocate()
        endmethod
      
        private method what_call takes nothing returns nothing
            set this.current = this.current + 1
            set this.elements = this.Steps[this.current]
            if this.elements == 0 or IsForceEmpty(this.toForce) then
                // If the cinematic was skipped just in the last line it won't be counted as skipped
                set this.skipped = IsForceEmpty(this.toForce) and this.elements != 0
                call this.finish()
            else
                if this.elements.isline then
                    call this.cinematic_line()
                else
                    call this.cinematic_actions()
                endif
            endif
        endmethod
      
        private static method callback takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            call this.what_call()
        endmethod
      
        //Where the magic happens
        private method cinematic_line takes nothing returns nothing
            local integer alpha = 0
            local real delay = 0.00
            local TransmissionElements what = this.elements
          
            if this.scene != null then
                set thistype.instance = this
                call TriggerExecute(this.scene)
                set thistype.instance = 0
            endif
          
            set this.played = what.Sound
            set bj_lastTransmissionDuration = GetTransmissionDuration(this.played, what.TimeType, what.Time)
          
            if IsPlayerInForce(thistype.LocalPlayer,this.toForce) then
                if this.played != null then
                    call StartSound(this.played)
                endif
                call SetCinematicScene(what.UnitType, what.Color, what.Name, what.Message, bj_lastTransmissionDuration + bj_TRANSMISSION_PORT_HANGTIME, bj_lastTransmissionDuration)
                set alpha = 255
            endif
            if what.Unit != null then
                call UnitAddIndicator(what.Unit, 255, 255, 255, alpha)
            endif
          
            if what.WillWait then
                set delay = bj_lastTransmissionDuration
            endif
          
            if delay > 0.00 then
                call TimerStart(this.t, delay, false, function thistype.callback)
            else
                call this.what_call()
            endif
          
            call what.destroy()
        endmethod
      
        private method cinematic_actions takes nothing returns nothing
            local TransmissionElements what = this.elements
          
            set thistype.instance = this
            if this.scene != null then
                call TriggerExecute(this.scene)
            endif
            if what.GetActions() != null then
                call TriggerExecute(what.GetActions())
            endif
            set thistype.instance = 0
          
            if what.Delay > 0.00 then
                call TimerStart(this.t, what.Delay, false, function thistype.callback)
            else
                call this.what_call()
            endif
          
            call what.destroy()
        endmethod
      
        //If the instance is 0 or a not allocated then none of this methods will run
      
        method Resume takes nothing returns nothing
            if this.Invalid("Resume") then
                return
            endif
          
            set this.paused = false
            call ResumeTimer(this.t)
            if IsPlayerInForce(thistype.LocalPlayer, this.toForce) then
                call StartSound(this.played)
            endif
            call SetSoundOffsetBJ(TimerGetElapsed(this.t), this.played) //In case of desync
        endmethod
      
        method Stop takes boolean fadeout returns nothing
            if this.Invalid("Stop") then
                return
            endif
          
            set this.paused = true
            call PauseTimer(this.t)
            call StopSound(this.played, false, fadeout)
        endmethod
      
        private static method NowResume takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            call this.Resume()
            call ReleaseTimer(GetExpiredTimer())
        endmethod
      
        method StopUntil takes boolean fadeout, real delay returns nothing
            if this.Invalid("StopUntil") then
                return
            endif
          
            call this.Stop(fadeout)
            if delay > 0.00 then
                call TimerStart(NewTimerEx(this), delay, false, function thistype.NowResume)
            else
                call this.Resume()
                debug call BJDebugMsg("Why do you even use this method?, Ay.")
            endif
        endmethod
      
        method Start takes nothing returns nothing
            if this.Invalid("Start") then
                return
            endif
          
            //If there is not a line or action so it just go to the end
            set this.current = 0
            set this.elements = 0
            call this.what_call()
        endmethod
      
        //Since I can't store codes I use triggers
        method AddEnd takes code func returns nothing
            if this.Invalid("AddEnd") then
                return
            endif
          
            if this.final == null then
                set this.final = CreateTrigger()
            endif
            call TriggerAddAction(this.final,func)
        endmethod
      
        //To add an action everytime a line or action is runned
        method AddSceneActions takes code func returns triggeraction
            if this.Invalid("AddSceneActions") then
                return null
            endif
          
            if this.scene == null then
                set this.scene = CreateTrigger()
            endif
            return TriggerAddAction(this.scene, func)
        endmethod
      
        //But if you wanna remove it
        method RemoveSceneActions takes triggeraction actions returns nothing
            if this.Invalid("RemoveSceneActions") then
                return
            endif
          
            call TriggerRemoveAction(this.scene,actions)
        endmethod
     
        //Methods to change the values individually
     
        private method LoadStep takes integer stepnumber returns TransmissionElements
            return this.Steps[stepnumber]
        endmethod
     
        private method LoadLine takes integer linenumber returns TransmissionElements
            return this.Lines[linenumber]
        endmethod
     
        private method LoadAction takes integer actionnumber returns TransmissionElements
            return this.Actions[actionnumber]
        endmethod
      
        //! textmacro SetGetValues takes FUNC1,FUNC2,TYPE,OPERATOR,ERROR1,ERROR2
        method $FUNC1$ByIndex takes integer stepnumber, $TYPE$ value returns nothing
            call this.Invalid($ERROR1$)
            set this.LoadStep(stepnumber).$OPERATOR$ = value
        endmethod
      
        method $FUNC2$ByIndex takes integer stepnumber returns $TYPE$
            call this.Invalid($ERROR2$)
            return this.LoadStep(stepnumber).$OPERATOR$
        endmethod
        //! endtextmacro
      
        //! runtextmacro SetGetValues("SetUnit","GetUnit","unit","Unit","\"SetUnit\"","\"GetUnit\"")
        //! runtextmacro SetGetValues("SetUnitType","GetUnitType","integer","UnitType","\"SetUnitType\"","\"GetUnitType\"")
        //! runtextmacro SetGetValues("SetColor","GetColor","playercolor","Color","\"SetColor\"","\"GetColor\"")
        //! runtextmacro SetGetValues("SetName","GetName","string","Name","\"SetName\"","\"GetName\"")
        //! runtextmacro SetGetValues("SetSound","GetSound","sound","Sound","\"SetSound\"","\"GetSound\"")
        //! runtextmacro SetGetValues("SetMessage","GetMessage","string","Message","\"SetMessage\"","\"GetMessage\"")
        //! runtextmacro SetGetValues("SetTimeType","GetTimeType","integer","TimeType","\"SetTimeType\"","\"GetTimeType\"")
        //! runtextmacro SetGetValues("SetTime","GetTime","real","Time","\"SetTime\"","\"GetTime\"")
        //! runtextmacro SetGetValues("SetWillWait","WillWait","boolean","WillWait","\"SetWillWait\"","\"WillWait\"")
        //! runtextmacro SetGetValues("SetDelay","GetDelay","real","Delay","\"SetDelay\"","\"GetDelay\"")
      
        method SetActionsByIndex takes integer stepnumber, code func returns nothing
            call this.Invalid("SetActionsByIndex")
            call this.LoadStep(stepnumber).SetActions(func)
        endmethod
      
        method GetActionsByIndex takes integer stepnumber returns trigger
            call this.Invalid("GetActionsByIndex")
            return this.LoadStep(stepnumber).GetActions()
        endmethod
      
        method AddActionsByIndex takes integer stepnumber, code func returns nothing
            call this.Invalid("AddMoreActionsByIndex")
            call this.LoadStep(stepnumber).AddActions(func)
        endmethod
      
        //If you wanna remove a step
        method RemoveStep takes integer stepnumber returns nothing
            local TransmissionElements what = this.Steps.removeFrom(stepnumber)
            local integer j
          
            if what.isline then
                set j = 1
                loop
                    exitwhen j > this.Lines.length
                    if what == this.Lines[j] then
                        call this.Lines.removeFrom(j)
                        exitwhen true
                    endif
                    set j = j + 1
                endloop
                set this.actual_line = this.Lines.length
                set j = stepnumber
                loop
                    exitwhen j > this.actual_line
                    set this.LoadLine(j).NumberLine = this.LoadLine(j).NumberLine - 1
                    set j = j + 1
                endloop
            else
                set j = 1
                loop
                    exitwhen j > this.Actions.length
                    if what == this.Actions[j] then
                        call this.Actions.removeFrom(j)
                        exitwhen true
                    endif
                    set j = j + 1
                endloop
                set this.actual_action = this.Actions.length
                set j = stepnumber
                loop
                    exitwhen j > this.actual_action
                    set this.LoadAction(j).NumberAction = this.LoadAction(j).NumberAction - 1
                    set j = j + 1
                endloop
            endif
            set this.actual_step = this.Steps.length
            set j = stepnumber
            loop
                exitwhen j > this.actual_step
                set this.LoadStep(j).NumberStep = this.LoadStep(j).NumberStep - 1
                set j = j + 1
            endloop
          
            call what.destroy()
        endmethod
      
        //If you wanna add a step in the middle of the steps list
        method InsertStep takes string whattype, integer stepnumber returns TransmissionElements
            local TransmissionElements node = TransmissionElements.create(whattype)
            local integer i
            local integer j
            if node == 0 then
                return 0
            endif
      
            if whattype=="line" then
                set node.NumberLine = this.actual_line
                set node.Unit = this.DefUnit
                set node.UnitType = this.DefUnitType
                set node.Color = this.DefColor
                set node.Name = this.DefName
                set node.Sound = this.DefSound
                set node.Message = this.DefMessage
                set node.Time = this.DefTime
                set node.TimeType = this.DefTimeType
                set node.WillWait = this.DefWillWait
                set i = 1
                set j = stepnumber
                loop
                    exitwhen j == 0
                    if this.LoadStep(j).isline then
                        set i = j + 1
                        exitwhen true
                    endif
                    set j = j - 1
                endloop
                call this.Lines.insertTo(node, i)
                set this.actual_line = this.Lines.length
                set j = i + 1
                loop
                    exitwhen j > this.actual_line
                    set this.LoadLine(j).NumberLine = this.LoadLine(j).NumberLine + 1
                    set j = j + 1
                endloop
                set node.NumberLine = i
            else
                set node.Delay = this.DefDelay
                set j = stepnumber
                set i = 1
                loop
                    exitwhen j == 0
                    if not this.LoadStep(j).isline then
                        set i = j + 1
                        exitwhen true
                    endif
                    set j = j - 1
                endloop
                call this.Actions.insertTo(node, i)
                set this.actual_action = this.Actions.length
                set j = i + 1
                loop
                    exitwhen j > this.actual_action
                    set this.LoadAction(j).NumberAction = this.LoadAction(j).NumberAction + 1
                    set j = j + 1
                endloop
                set node.NumberAction = i
            endif
            call this.Steps.insertTo(node, stepnumber)
            set this.actual_step = this.Steps.length
            set j = stepnumber
            loop
                exitwhen j > this.actual_step
                set this.LoadStep(j).NumberStep = this.LoadStep(j).NumberStep + 1
                set j = j + 1
            endloop
            set node.NumberStep = stepnumber
          
            return node
        endmethod
      
        //To set a line with all the needed values (at the beggining)
      
        method AddLine takes unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait returns TransmissionElements
            local TransmissionElements what
            if this.Invalid("AddLine") then
                return 0
            endif
          
            set what = TransmissionElements.create_line(whichUnit, whichColor, unitName, soundHandle, message, timeType, timeVal, wait)
            call this.Lines.insert(what)
            set this.actual_line = this.Lines.length
            set what.NumberLine = this.actual_line
            call this.Steps.insert(what)
            set this.actual_step = this.Steps.length
            set what.NumberStep = this.actual_step
          
            return what
        endmethod
      
        method AddLineById takes integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait returns TransmissionElements
            local TransmissionElements what
            if this.Invalid("AddLineById") then
                return 0
            endif
          
            set what = this.AddLine(null, whichColor, unitName, soundHandle, message, timeType, timeVal, wait)
            set what.UnitType=id
          
            return what
        endmethod
      
        //To add an action and (maybe) a delay to the next action or line
        method AddAction takes real delay, code actions returns TransmissionElements
            local TransmissionElements what
            if this.Invalid("AddAction") then
                return 0
            endif
          
            set what = TransmissionElements.create_action(actions, delay)
            call this.Actions.insert(what)
            set this.actual_action = this.Actions.length
            set what.NumberAction = this.actual_action
            call this.Steps.insert(what)
            set this.actual_step = this.Steps.length
            set what.NumberStep = this.actual_step
            return what
        endmethod
      
        method AddStep takes string whattype returns TransmissionElements
            local TransmissionElements what
            if this.Invalid("AddStep") then
                return 0
            endif
          
            set what = TransmissionElements.create(whattype)
          
            if what == 0 then
                return 0
            endif
      
            if whattype == "line" then
                set what.NumberLine = this.actual_line
                set what.Unit = this.DefUnit
                set what.UnitType = this.DefUnitType
                set what.Color = this.DefColor
                set what.Name = this.DefName
                set what.Sound = this.DefSound
                set what.Message = this.DefMessage
                set what.Time = this.DefTime
                set what.TimeType = this.DefTimeType
                set what.WillWait = this.DefWillWait
                call this.Lines.insert(what)
                set this.actual_line = this.Lines.length
                set what.NumberLine = this.actual_line
            else
                set what.Delay = this.DefDelay
                call this.Actions.insert(what)
                set this.actual_action = this.Actions.length
                set what.NumberAction = this.actual_action
            endif
          
            call this.Steps.insert(what)
            set this.actual_step = this.Steps.length
            set what.NumberStep = this.actual_step
          
            return what
        endmethod
      
        //Main part of the system
        private static method AddPlayer takes nothing returns nothing
            local thistype this = thistype.instance
            local player p = GetEnumPlayer()
            local integer i = GetPlayerId(p)
            set this.toPlayer = p
            call ForceAddPlayer(this.toForce,p)
            call ForceAddPlayer(this.OriginalTargetForce,p)
            call AllInstances[i].insert(this)
            set p = null
        endmethod
      
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
              
            set this.allocated = true
            set this.skipped = false
            set this.paused = false
            set this.toForce = CreateForce()
            set this.OriginalTargetForce = CreateForce()
            set this.t = NewTimerEx(this)
            set this.actual_line = 0
            set this.actual_action = 0
            set this.actual_step = 0
            set this.ended = false
            set this.Lines = IntegerList.create()
            set this.Actions = IntegerList.create()
            set this.Steps = IntegerList.create()
          
            return this
        endmethod
      
        static method CreateEx takes force toForce, integer data returns thistype
            local thistype this = thistype.create()
          
            if toForce != null and not IsForceEmpty(toForce) then
                set thistype.instance = this
                call ForForce(toForce, function thistype.AddPlayer)
                set thistype.instance = 0
            endif
              
            set this.data = data
          
            return this
        endmethod
      
        // You can edit the target force and the data
      
        method SetTargetForce takes force toForce returns nothing
            if this.Invalid("SetTargetForce") then
                return
            endif
            if toForce!=null and not IsForceEmpty(toForce) then
                set thistype.instance = this
                call ForForce(toForce, function thistype.AddPlayer)
                set thistype.instance = 0
            endif
        endmethod
      
        // To add a player or force to the target force just use ForceAddPlayer(this.toForce, <your player>) or the added ForceAddForce(this.toForce,<your force>)
      
        // If a player skips cinematic he will be removed from every instance trasnmission where is in
        static method onActions takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local integer i = GetPlayerId(p)
            local integer j = 1
            local thistype this
            loop
                exitwhen j > AllInstances[i].length
                set this = AllInstances[i][j]
                if this.allocated then
                    if IsPlayerInForce(p, this.toForce) then
                        call ForceRemovePlayer(this.toForce, p)
                        if thistype.LocalPlayer == p then
                            // I don't know if this is free of desync (I checked and there is not desync yet)
                            call EndCinematicScene()
                            if this.played != null then
                                call StopSound(this.played, false, true)
                            endif
                        endif
                    endif
                    if IsForceEmpty(this.toForce) then
                        call PauseTimer(this.t)
                        call TimerStart(this.t, RMinBJ(bj_TRANSMISSION_PORT_HANGTIME, TimerGetRemaining(this.t)), false, function thistype.callback)
                    endif
                endif
              
                set j = j + 1
            endloop
            call AllInstances[i].clear()
            set p = null
        endmethod
      
        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local integer i = 0
            loop
                exitwhen i > PLAYER_NEUTRAL_AGGRESSIVE
                set What_Force[i] = GetForceOfPlayer(Player(i)) //To use it if you wanna send the transmission just for 1 player
                set AllInstances[i] = IntegerList.create()
                call TriggerRegisterPlayerEvent(t, Player(i),EVENT_PLAYER_END_CINEMATIC)
                set i = i + 1
            endloop
            call TriggerAddAction(t, function thistype.onActions)
            set t = null
            //-- --
            set thistype.LocalPlayer = GetLocalPlayer()
            call ForceCinematicSubtitles(true)
        endmethod
    endmodule
  
    struct Transmission extends array
        implement Alloc
        implement M
    endstruct
  
endlibrary
Lua version:
Lua:
do -- version 1.0.0
    --If you wanna the system display its error message set this value to true
    local DEBUG_MODE = false
    --[[
    Also requires the Lua Timer Utils. https://www.hiveworkshop.com/threads/lua-timerutils.316957/
    How it works:
        a value of type Transmission and use the function create like this:
 
            local curr = Transmission.create()
         
        also you can set the target force of the transmission using:
         
            curr:SetTargetForce(toForce) This not assing a force, just move the players of the input to the actually target force, so using a function that creates a force
                                         in the argument toForce will cause an object leak.
         
        you can also save a value to use during the transmission using:
 
            curr.Data = value
         
        if you wanna add or remove a player or a force to the target force just use the normal functions ForceAddPlayer, ForceRemovePlayer and the added ForceAddForce, ForceRemoveForce
     
        To reduce the process use:
 
            local curr = Transmission.create(toForce, data)
     
       Then to add a dialogue line just do:
     
            curr:AddLine(whichUnit, color, unitName, soundHandle, message, timeType, timeVal, wait)
         
          "whichUnit": The unit who do the dialogue line.
          "color": The color of the unit in the dialogue (if you set this value to nil so then, if you have the StoreUnitColor library the unit will have their actual color, if not, it will have their owner color).
          "unitName": The name of that unit.
          "soundHandle": The sound that will be played during the dialogue line.
          "message": What the unit will say.
          "timeType": Is like the GUI function, you can use 0 (Transmission.ADD), 1 (Transmission.SET), 2 (Transmission.SUB), another value will make the wait time to 0.
          "timeVal": Is like the GUI function, the normal duration of the transmission is the duration of the sound and you can edit the value depending of this and the previous value.
          "wait": Is like the GUI function, if this is true the next transmission will be wait the time depending of the previous two values.
     
        In case you don't wanna set a unit, but instead a unit-type you can use curr.AddLinebyId instead (default color BLACK)
 
        Also you can add actions between the lines using the function:
     
            curr:AddAction(delay, actions)
         
          "delay": If you wanna add a delay to the next action or line.
          "actions": The function that will run.
       
        If you wanna the action run in the exact moment a line is happening just set the delay to 0.00 and put it right before the line.
        If you wanna just add an extra delay just put nil in the actions part
     
        I have to say this steps are assinged an index by the order they were declared that is the number of the step.
 
        You can also add an action to the end of the transmission (this function will run yes or yes even you skip the transmission)  using:
 
            curr:AddEnd(func)
         
        Where you can use the values curr=Transmission.instance to reffer to the actual instance, and with that use curr.Data if you stored one and curr.Skipped to detect
        if the cinematic was skipped but if was skipped in the last line this value will remain false.
         
        After set the lines the function
 
            curr:Start()
         
        To start the transmission, and to skip the transmission (the end function will run anyway) just press the ESC button,
        (this will skip all the transmissions where the player is) but if the implied force has more than one player the process will continue
        to the rest of players on that force (.toForce is empty). The transmission will end only if all the implied players skip the transmission (So if you use "All players"
        probably you never won't skip the transmission) but you can use the function Force(player p) to have a force just for 1 player (Don't destroy those forces).
 
        If you wanna you can also pause the transmission using:
         
            curr:Stop(fadeout) --For the actual played sound.
         
        or:
 
            curr:Stop(fadeout, delay) --If you wanna the transmission resume itself after a while
         
        And to resume manually use:
 
            curr:Resume()
         
        Note: The transsmision and its elements are not stored after start it, they are recycled once the transmission ends.
 
        Other members:
 
            toForce, to reffer the force to which it is addressed the transmission.
            OriginalTargetForce, if you edited the force toForce, you can have the original force with this.
            toPlayer, if the transmission is just for one player you can use this instead, in other case this will return the last added player. --(I don't know the order)
            LastLineNumber, is the number of the last line what was transmitted.
            NextLineNumber, is the number of the next line what will be transmitted.
            ActionNumber, is the number of the actions those are running.
            StepNumber, is the number of the step that is running (line or actions).
            InUse, returns if the instance is allocated.
            Paused, returns if the transmission is paused.
            elements, to reffer to the elements instance of the actual step. Please don't edit this value
         
            method AddSceneActions(func)
                If you wanna add an action that runs everytime an step (line or action except the end) is runned, this also can be a replace of .AddAction in some
                moments if you compare the number of the step with StepNumber, to know if is a line or action use the member boolean IsLine
                and if is true you can use the member integer LineNumber to know the number of the actual line.
             
            method RemoveSceneActions(func)
                This remove the actions that you added before.
             
            If you wanna set the elements by separated you can use the member
         
            method AddStep(whattype) returns TransmissionElements
                This add an step that also have an index by the order is placed, the input only can be "line" and "action"
             
            method AddAnotherStep(stepnumber,whattype) returns TransmissionElements
                This add an step but the index is set manually, if there are steps that were set after this and after the transmission was started
                those index will be moved.
           
            Just if you wanna remove one
         
            method RemoveStep(stepnumber,reorder)
                This remove an step using the number of the step, in this case the reasing of the next steps is optional because here is no risk of overwrite.
         
            After add the step the returned value have their own members
         
            Unit   This is the unit who will be marked during the transmission (not who will apear in the transmission).
            UnitType
            Color
            Name
            Sound
            Message
            Time
            TimeType
            WillWait
            Delay
         
            NumberLine
            NumberAction
            NumberStep
         
            method SetActions(func)     This replace all the actions
            method AddMoreAction(func)  This add action without affecting the added actions
            method GetActions() returns a Array<functions>
         
            But if you wanna use this members using the number of the step use their "ByIndex" methods (these are members of Transmission not of TransmissionElements)
            You have to do:
         
                curr:$Name of the member$ByIndex(stepnumber,$type of the member$)
             
                $type of the member$=curr:$Name of the member$ByIndex(stepnumber)
             
            Example:
             
                local curr = Transmission.create()
                curr:AddStep("line")    --is the step 1
                curr:AddStep("action")  --is the step 2
                curr:SetUnitByIndex(1, <your unit>)
                curr:SetActionsByIndex(2, <your actions>)
             
                A replace of:
             
                local curr = Transmission.create()
                local what
                what = curr:AddStep("line")
                what:SetUnit(<your unit>)
                what = curr:AddStep("action")
                what:SetActions(<your actions>)
         
            If you wanna get the TransmissionElements instance using that index just do:
         
                <variable> = curr:LoadElement(stepnumber)
             
            But if you wanna save the number of lines you can set default values (before than create the steps)
         
            DefUnit
            DefUnitType
            DefColor
            DefName
            DefSound
            DefMessage
            DefTime
            DefTimeType
            DefWillWait
            DefDelay
             
    ]]--
 
    --A function to use
    local Temp2 = 0
    local What_Force = {}
    local Other = nil
 
    function IsForceEmpty(f)
        if f then
            local Temp = 0
            ForForce(f, function ()
                Temp = Temp + 1
            end)
            return Temp == 0
        end
        return true
    end
 
    function ForceAddForce(principal, other)
        if not principal or not other then
            return
        else
            ForForce(principal ,function ()
                ForceAddPlayer(other,GetEnumPlayer())
            end)
        end
    end
 
    function ForceRemoveForce(principal, other)
        if not principal or not other then
            return
        else
            ForForce(principal ,function ()
                ForceRemovePlayer(other,GetEnumPlayer())
            end)
        end
    end

    function Force(p)
        return What_Force[GetPlayerId(p)]
    end

    function DebugError(msg)
        if DEBUG_MODE then
            error(msg, 3)
        else
            error("")
        end
    end

    -- This are the elements of each line transmission
    TransmissionElementsMT = {
        __index = function (t, k)
            -- Define only what fields can have your object
            if TransmissionElementsMT[k] then
                return TransmissionElementsMT[k]
            elseif k == "isline" or
                k == "NumberStep" then
                return rawget(t, k)
            elseif k == "Unit" or
                k == "UnitType" or
                k == "Color" or
                k == "Name" or
                k == "Sound" or
                k == "Message" or
                k == "TimeType" or
                k == "Time" or
                k == "WillWait" or
                k == "NumberLine" then
                if not rawget(t, "isline") then
                    DebugError("This is an action")
                end
                return rawget(t, k)
            elseif k == "Delay" or
                k == "actions" or
                k == "NumberAction" then
                if rawget(t, "isline") then
                    DebugError("This is a line")
                end
                return rawget(t, k)
            else
                DebugError("This field doesn't exist " .. tostring(k))
            end
        end,
 
        __newindex = function (t, k , v)
            -- Define only what fields can have your object
            if TransmissionElementsMT[k] then
                DebugError("You can't edit this method")
            elseif k == "isline" or
                k == "NumberStep" then
                rawset(t, k, v)
            elseif k == "Unit" or
                k == "UnitType" or
                k == "Color" or
                k == "Name" or
                k == "Sound" or
                k == "Message" or
                k == "TimeType" or
                k == "Time" or
                k == "WillWait" or
                k == "NumberLine" then
                if not rawget(t, "isline") then
                    DebugError("This is an action")
                end
                rawset(t, k, v)
            elseif k == "Delay" or
                k == "actions" or
                k == "NumberAction" then
                if rawget(t, "isline") then
                    DebugError("This is a line")
                end
                rawset(t, k, v)
            else
                DebugError("You can't add new fields " .. tostring(k))
            end
        end,
 
        __name = "TransmissionElements",
 
        __tostring = function (t)
            local result = ""
            if t.isline then
                result = result .. "Unit: " .. tostring(t.Unit) .. "\n"
                result = result .. "UnitType: " .. tostring(t.UnitType) .. "\n"
                result = result .. "Color: " .. tostring(t.Color) .. "\n"
                result = result .. "Name: " .. tostring(t.Name) .. "\n"
                result = result .. "Sound: " .. tostring(t.Message) .. "\n"
                result = result .. "TimeType: " .. tostring(t.TimeType) .. "\n"
                result = result .. "Time: " .. tostring(t.Time) .. "\n"
                result = result .. "WillWait: " .. tostring(t.WillWait) .. "\n"
            else
                result = result .. "Delay: " .. tostring(t.Delay) .. "\n"
                for k, v in ipairs(t.actions) do
                    result = result .. "Action ".. k .. ": " .. tostring(v) .. "\n"
                end
            end
            return result
        end
    }

    function TransmissionElementsMT:SetActions(func)
        if self.isline then
            DebugError("This is a line |cffff0000SetActions|r")
        end
        self.actions = {}
        if func then
            table.insert(self.actions, func)
        end
    end
 
    function TransmissionElementsMT:AddMoreActions(func)
        if self.isline then
            DebugError("This is a line |cffff0000AddMoreActions|r")
        -- Only add actions if func is not nil and self.actions was created previously
        elseif  not func then
            return
        elseif not self.actions then
            DebugError("Why do you wanna add \"more\" actions if there isn't even a trigger with actions?")
        else
            table.insert(self.actions, func)
        end
    end
     
    function TransmissionElementsMT:GetActions()
        if self.isline then
            DebugError("This is a line |cffff0000GetActions|r")
        end
        return self.actions
    end

    TransmissionElements = setmetatable({}, {
        __index = function (t, k)
            if k == "create" or
                k == "create_action" or
                k == "create_line" then
                rawget(t, k)
            else
                DebugError("This field don't exist " .. tostring(k))
            end
        end,

        __newindex = function (t, k, v)
            -- Define only what fields can have your class
            if k == "create" or
                k == "create_action" or
                k == "create_line"  then
                rawset(t, k , v)
            else
                DebugError("You can't add new fields " .. tostring(k))
            end
        end,
 
        __name = "TransmissionElements",
 
        __tostring = function (t)
            return "Class: TransmissionElements"
        end
    })

    function TransmissionElements.create(whattype)
        local self = setmetatable({}, TransmissionElementsMT)
     
        if whattype == "line" then
            self.isline = true
        elseif whattype == "action" then
            self.isline = false
        else
            DebugError("Invalid type.")
        end
     
        return self
    end

    function TransmissionElements.create_action(delay, func)
        local self = TransmissionElements.create("action")
     
        -- Now I can store functions :D
        if not self.actions then
            self.actions = {}
        end
        if func then
            table.insert(self.actions, func)
        end
        self.Delay = delay
     
        return self
    end

    function TransmissionElements.create_line(whichUnit, whichColor, unitName, soundHandle, message, timeType, timeVal, wait)
        local self = TransmissionElements.create("line")
     
        self.Unit = whichUnit
        self.Name = unitName
        self.Sound = soundHandle
        self.Message = message
        self.TimeType = timeType
        self.Time = timeVal
        self.WillWait = wait
        if not whichUnit then
            self.UnitType = 0
            if not whichColor then
                if LIBRARY_StoreUnitColor then
                    self.Color = PLAYER_COLOR_BLACK
                else
                    self.Color = ConvertPlayerColor(PLAYER_NEUTRAL_AGGRESSIVE)
                end
            else
                self.Color = whichColor
            end
        else
            self.UnitType = GetUnitTypeId(whichUnit)
            if not whichColor then
                if LIBRARY_StoreUnitColor then
                    self.Color = GetUnitColor(whichUnit)
                else
                    self.Color = GetPlayerColor(GetOwningPlayer(whichUnit))
                end
            else
                self.Color = whichColor
            end
        end
     
        return self
    end

    --*****************************************************
    --********The actual struct of the transmission********
    --*****************************************************

    local LocalPlayer = nil

    local AllInstances = {}
    for i = 0, PLAYER_NEUTRAL_AGGRESSIVE do
        AllInstances[Player(i)] = {}
    end

    TransmissionMT = {
        __index = function (t, k)
            -- Define only what fields can have your object
            if TransmissionMT[k] then
                return TransmissionMT[k]
            elseif k == "toPlayer" or
                k == "toForce" or
                k == "OriginalTargetForce" or
                k == "elements" or
                k == "DefUnit" or
                k == "DefUnitType" or
                k == "DefColor" or
                k == "DefName" or
                k == "DefSound" or
                k == "DefMessage" or
                k == "DefTime" or
                k == "DefTimeType" or
                k == "DefWillWait" or
                k == "DefDelay" or
                k == "Data" or
                k == "Paused" or
                k == "Skipped" or
                k == "played" or
                k == "final" or
                k == "t" or
                k == "actual_line" or
                k == "actual_action" or
                k == "actual_step" or
                k == "StepNumber" or
                k == "ActionNumber" or
                k == "LineNumber" or
                k == "ended" or
                k == "scene" or
                k == "Steps" or
                k == "Lines" or
                k == "Actions" then
                return rawget(t, k)
            else
                DebugError("This field doesn't exist " .. tostring(k))
            end
        end,
 
        __newindex = function (t, k , v)
            -- Define only what fields can have your object
            if TransmissionMT[k] then
                DebugError("You can't edit this method")
            elseif k == "toPlayer" or
                k == "toForce" or
                k == "OriginalTargetForce" or
                k == "elements" or
                k == "DefUnit" or
                k == "DefUnitType" or
                k == "DefColor" or
                k == "DefName" or
                k == "DefSound" or
                k == "DefMessage" or
                k == "DefTime" or
                k == "DefTimeType" or
                k == "DefWillWait" or
                k == "DefDelay" or
                k == "Data" or
                k == "Paused" or
                k == "Skipped" or
                k == "played" or
                k == "final" or
                k == "t" or
                k == "actual_line" or
                k == "actual_action" or
                k == "actual_step" or
                k == "StepNumber" or
                k == "ActionNumber" or
                k == "LineNumber" or
                k == "ended" or
                k == "scene" or
                k == "Steps" or
                k == "Lines" or
                k == "Actions" then
                rawset(t, k, v)
            else
                DebugError("You can't add new fields " .. tostring(k))
            end
        end,

        __len = function (t)
            return #t.Steps
        end,
 
        __name = "Transmission",

        __tostring = function (t)
            local result = ""
            for _, v in ipairs(t.Steps) do
                result = result .. tostring(v)
            end
            return result
        end
    }
    -- When the transmission ends
 
    function TransmissionMT:finish()
        ReleaseTimer(self.t)
        self.ended = true
        if self.final then
            Transmission.instance = self
            for _, func in ipairs(self.final) do
                func()
            end
            Transmission.instance = nil
            self.final = nil
        end
        ForForce(self.toForce,function ()
            for k, v in ipairs(AllInstances[GetEnumPlayer()]) do
                if v == self then
                    table.remove(AllInstances[GetEnumPlayer()], k)
                    break
                end
            end
        end)
        DestroyForce(self.OriginalTargetForce)
        DestroyForce(self.toForce)
        self.Steps = nil
        self.Lines = nil
        self.Actions = nil
        self.scene = nil
        self.elements = nil
        self.scene = nil
        self.Data = nil
        self.played = nil
        self.toForce = nil
        self.toPlayer = nil
        self.t = nil
    end
     
    function TransmissionMT:what_call()
        self.StepNumber = self.StepNumber + 1
        self.elements = self.Steps[self.StepNumber]
        if not self.elements or IsForceEmpty(self.toForce) then
            -- If the cinematic was skipped just in the last line it won't be counted as skipped
            self.Skipped = IsForceEmpty(self.toForce) and self.elements
            self:finish()
        else
            if self.elements.isline then
                self.LineNumber = self.LineNumber + 1
                self:cinematic_line()
            else
                self.ActionNumber = self.ActionNumber + 1
                self:cinematic_actions()
            end
        end
    end
 
    local function callback()
        GetTimerData():what_call()
    end

    -- Where the magic happens
    function TransmissionMT:cinematic_line()
        local alpha = 0
        local delay = 0.00
        local what = self.elements
     
        self.played = what.Sound
        bj_lastTransmissionDuration = GetTransmissionDuration(self.played, what.TimeType, what.Time)
     
        if IsPlayerInForce(LocalPlayer, self.toForce) then
            if self.played then
                StartSound(self.played)
            end
            SetCinematicScene(what.UnitType, what.Color, what.Name, what.Message, bj_lastTransmissionDuration + bj_TRANSMISSION_PORT_HANGTIME, bj_lastTransmissionDuration)
            alpha = 255
        end
        if what.Unit then
            UnitAddIndicator(what.Unit, 255, 255, 255, alpha)
        end
     
        if self.scene then
            Transmission.instance = self
            for _, func in ipairs(self.scene) do
                func()
            end
            Transmission.instance = nil
        end
     
        if what.WillWait then
            delay = bj_lastTransmissionDuration
        end
     
        if delay > 0.00 then
            TimerStart(self.t, delay, false, callback)
        else
            self:what_call()
        end
    end
 
    function TransmissionMT:cinematic_actions()
        local what = self.elements
     
        Transmission.instance = self
        pcall(function ()
            for _, func in ipairs(what:GetActions()) do
                func()
            end
        end)
     
        if self.scene then
            for _, func in ipairs(self.scene) do
                func()
            end
        end
        Transmission.instance = nil
     
        if what.Delay > 0.00 then
            TimerStart(self.t, what.Delay, false, callback)
        else
            self:what_call()
        end
    end

    function TransmissionMT:Resume()
        if self.Paused then
            self.Paused=false
            ResumeTimer(self.t)
            if IsPlayerInForce(LocalPlayer, self.toForce) then
                StartSound(this.played)
            end
            SetSoundOffsetBJ(TimerGetElapsed(self.t), self.played) -- In case of desync
        end
    end

    function TransmissionMT:Stop(fadeout, delay)
        self.Paused = true
        PauseTimer(self.t)
        StopSound(self.played, false, fadeout)
        if delay then
            if delay>0.00 then
                TimerStart(NewTimer(), delay, false, function ()
                    self:Resume()
                    ReleaseTimer()
                end)
            else
                self:Resume()
                warn("Why do you even use this method?, Ay.")
            end
        end
    end
 
    function TransmissionMT:Start()
        -- If there is not a line or action so it just go to the end
        self.LineNumber = 0
        self.ActionNumber = 0
        self.StepNumber = 0
        self.elements = nil
        self:what_call()
    end
     
    -- Now I can save functions :D
    function TransmissionMT:AddEnd(func)
        if not self.final then
            self.final = {}
        end
        table.insert(self.final, func)
    end
 
    -- To add an action everytime a line or action is runned
    function TransmissionMT:AddSceneActions(func)
        if not self.scene then
            self.scene = {}
        end
        table.insert(self.scene, func)
        return #self.scene
    end
 
    -- But if you wanna remove it
    function TransmissionMT:RemoveSceneActions(actions)
        table.remove(self.scene, actions)
    end

    --To set and get the values using their step position

    function TransmissionMT:SetUnitByIndex(stepnumber, value)
        self.Steps[stepnumber].Unit = value
    end
 
    function TransmissionMT:GetUnitByIndex(stepnumber)
        return self.Steps[stepnumber].Unit
    end

    function TransmissionMT:SetUnitTypeByIndex(stepnumber, value)
        self.Steps[stepnumber].UnitType = value
    end
 
    function TransmissionMT:GetUnitTypeByIndex(stepnumber)
        return self.Steps[stepnumber].UnitType
    end

    function TransmissionMT:SetColorByIndex(stepnumber, value)
        self.Steps[stepnumber].Color = value
    end
 
    function TransmissionMT:GetColorByIndex(stepnumber)
        return self.Steps[stepnumber].Color
    end

    function TransmissionMT:SetNameByIndex(stepnumber, value)
        self.Steps[stepnumber].Name = value
    end
 
    function TransmissionMT:GetNameByIndex(stepnumber)
        return self.Steps[stepnumber].Name
    end

    function TransmissionMT:SetSoundByIndex(stepnumber, value)
        self.Steps[stepnumber].Sound = value
    end
 
    function TransmissionMT:GetSoundByIndex(stepnumber)
        return self.Steps[stepnumber].Sound
    end

    function TransmissionMT:SetMessageByIndex(stepnumber, value)
        self.Steps[stepnumber].Message = value
    end
 
    function TransmissionMT:GetMessageByIndex(stepnumber)
        return self.Steps[stepnumber].Message
    end

    function TransmissionMT:SetTimeTypeByIndex(stepnumber, value)
        self.Steps[stepnumber].TimeType = value
    end
 
    function TransmissionMT:GetTimeTypeByIndex(stepnumber)
        return self.Steps[stepnumber].TimeType
    end

    function TransmissionMT:SetTimeByIndex(stepnumber, value)
        self.Steps[stepnumber].Time = value
    end
 
    function TransmissionMT:GetTimeByIndex(stepnumber)
        return self.Steps[stepnumber].Time
    end

    function TransmissionMT:SetWillWaitByIndex(stepnumber, value)
        self.Steps[stepnumber].WillWait = value
    end
 
    function TransmissionMT:WillWaitByIndex(stepnumber)
        return self.Steps[stepnumber].WillWait
    end

    function TransmissionMT:SetDelayByIndex(stepnumber, value)
        self.Steps[stepnumber].Delay = value
    end
 
    function TransmissionMT:GetDelayByIndex(stepnumber)
        return self.Steps[stepnumber].Delay
    end
 
    function TransmissionMT:SetActionsByIndex(stepnumber, func)
        self.Steps[stepnumber].SetActions(func)
    end
 
    function TransmissionMT:GetActionsByIndex(stepnumber)
        self.Steps[stepnumber].GetActions()
    end
 
    function TransmissionMT:AddMoreActionsByIndex(stepnumber, func)
        self.Steps[stepnumber].AddMoreActions(func)
    end
     
    --If you wanna remove a step
    function TransmissionMT:RemoveStep(stepnumber)
        local what = table.remove(self.Steps, stepnumber)
     
        if what.isline then
            for k, v in ipairs(self.Lines) do
                if what == v then
                    table.remove(self.Lines, k)
                    break
                end
            end
            self.actual_line = #self.Lines
            for j = stepnumber, self.actual_line do
                self.Lines[j].NumberLine = self.Steps[j].NumberLine - 1
            end
        else
            for k, v in ipairs(self.Actions) do
                if what == v then
                    table.remove(self.Actions, k)
                    break
                end
            end
            self.actual_action = #self.Actions
            for j = stepnumber, self.actual_action do
                self.Actions[j].NumberAction = self.Steps[j].NumberAction - 1
            end
        end
        self.actual_step = #self.Steps
        for j = stepnumber, self.actual_step do
            self.Steps[j].NumberStep = self.Steps[j].NumberStep - 1
        end

        return what
    end
     
    -- If you wanna add another step
    function TransmissionMT:AddAnotherStep(stepnumber, whattype)
        local node = TransmissionElements.create(whattype)
     
        -- Insert to list
        table.insert(self.Steps, stepnumber, node)
        node.NumberStep = stepnumber
 
        local i = 0  -- To look in what part I should insert the line or action
        if whattype == "line" then
            node.Unit = self.DefUnit
            node.UnitType = self.DefUnitType or 0
            node.Color = self.DefColor
            node.Name = self.DefName or ""
            node.Sound = self.DefSound
            node.Message = self.DefMessage or ""
            node.Time = self.DefTime or 0.00
            node.TimeType = self.DefTimeType or 1
            node.WillWait = self.DefWillWait
            for j = stepnumber, 0, -1 do
                if self.Steps[j].isline then
                    i = j + 1
                    break
                end
            end
            table.insert(self.Lines, i, node)
            self.actual_line = #self.Lines
            self.NumberLine = i
        else
            node.Delay = self.DefDelay or 0.00
            for j = stepnumber, 0, -1 do
                if not self.Steps[j].isline then
                    i = j + 1
                    break
                end
            end
            table.insert(self.Actions, i, node)
            self.actual_action = #self.Actions
            self.NumberAction = i
        end
     
        return node
    end

    -- To set a line with all the needed values (at the beggining)
     
    function TransmissionMT:AddLine(whichUnit, whichColor, unitName, soundHandle, message, timeType, timeVal, wait)
        local what = TransmissionElements.create_line(whichUnit, whichColor, unitName, soundHandle, message, timeType, timeVal, wait)
        table.insert(self.Lines, what)
        self.actual_line = #self.Lines
        what.NumberLine = self.actual_line
        table.insert(self.Steps, what)
        self.actual_step = #self.Steps
        what.NumberStep = self.actual_step
     
        return what
    end
 
    function TransmissionMT:AddLineById(id, whichColor, unitName, soundHandle, message, timeType, timeVal, wait)
        local what = self:AddLine(nil, whichColor, unitName, soundHandle, message, timeType, timeVal, wait)
        what.UnitType = id
     
        return what
    end
 
    -- To add an action and (maybe) a delay to the next action or line
    function TransmissionMT:AddAction(delay, actions)
        local what = TransmissionElements.create_action(delay, actions)
        table.insert(self.Actions, what)
        self.actual_action = #self.Actions
        table.insert(self.Steps, what)
        self.actual_step = #self.Steps
        what.NumberStep = self.actual_step
     
        return what
    end

    function TransmissionMT:AddStep(whattype)
        local what = TransmissionElements.create(whattype)
 
        if whattype == "line" then
            table.insert(self.Lines, what)
            self.actual_line = #self.Lines
            what.NumberLine = self.actual_line
            what.Unit = self.DefUnit
            what.UnitType = self.DefUnitType or 0
            what.Color = self.DefColor
            what.Name = self.DefName or ""
            what.Sound = self.DefSound
            what.Message = self.DefMessage or ""
            what.Time = self.DefTime or 0.00
            what.TimeType = self.DefTimeType or 1
            what.WillWait = self.DefWillWait
        else
            table.insert(self.Actions, what)
            self.actual_action = #self.Actions
            what.Delay = self.DefDelay or 0.00
        end
     
        table.insert(self.Steps, what)
        self.actual_step = #self.Steps
        what.NumberStep = self.actual_step
     
        return what
    end
     
    -- You can edit the target force and the data

    function TransmissionMT:SetTargetForce(toForce)
        if toForce and not IsForceEmpty(toForce) then
            ForForce(toForce, function ()
                local p = GetEnumPlayer()
                self.toPlayer = p
                ForceAddPlayer(self.toForce, p)
                ForceAddPlayer(self.OriginalTargetForce, p)
                table.insert(AllInstances[p], self)
            end)
        end
    end

    Transmission = setmetatable({
        ADD = bj_TIMETYPE_ADD, -- 0
        SET = bj_TIMETYPE_SET, -- 1
        SUB = bj_TIMETYPE_SUB, -- 2
    }, {
        __index = function (t, k)
            if k == "create" or
                k == "ADD" or
                k == "SET" or
                k == "SUB" or
                k == "instance" then
                rawget(t, k)
            else
                DebugError("This field doesn't exist " .. tostring(k))
            end
        end,

        __newindex = function (t, k, v)
            -- Define only what fields can have your class
            if k == "ADD" or
                k == "SET" or
                k == "SUB" then
                DebugError("This value is a constant " .. tostring(k))
            elseif k == "create" or
                k == "instance" then
                rawset(t, k , v)
            else
                DebugError("You can't add new fields " .. tostring(k))
            end
        end,
 
        __name = "Transmission",
 
        __tostring = function (t)
            return "Class: Transmission"
        end
    })

    function Transmission.create(toForce, data)
        if toForce and not data then
            DebugError("Insufficient arguments")
        end
        local self = setmetatable({}, TransmissionMT)
        self.Skipped = false
        self.Paused = false
        self.toForce = CreateForce()
        self.OriginalTargetForce = CreateForce()
        self.t = NewTimer(self)
        self.actual_line = 0
        self.actual_action = 0
        self.actual_step = 0
        self.ended = false

        self.Steps = {}
        self.Lines = {}
        self.Actions = {}

        if toForce and data then
            if not IsForceEmpty(toForce) then
                ForForce(toForce, function ()
                    local p = GetEnumPlayer()
                    self.toPlayer = p
                    ForceAddPlayer(self.toForce, p)
                    ForceAddPlayer(self.OriginalTargetForce, p)
                    table.insert(AllInstances[p], self)
                end)
            end
            self.Data = data
        end

        return self
    end

    -- To add a player or force to the target force just use ForceAddPlayer(this.toForce, <your player>) or the added ForceAddForce(this.toForce, <your force>)
 
    local oldinit = InitBlizzard
 
    function InitBlizzard()
        local t = CreateTrigger()
        for i = 0, PLAYER_NEUTRAL_AGGRESSIVE do
            What_Force[i] = GetForceOfPlayer(Player(i)) -- To use it if you wanna send the transmission just for 1 player
            TriggerRegisterPlayerEvent(t, Player(i), EVENT_PLAYER_END_CINEMATIC)
        end
        -- If a player skips cinematic he will be removed from every instance trasnmission where is in
        TriggerAddAction(t, function ()
            local p = GetTriggerPlayer()
            for _, self in ipairs(AllInstances[p]) do
                if IsPlayerInForce(p, self.toForce) then
                    ForceRemovePlayer(self.toForce, p)
                    if p == LocalPlayer then
                        -- I don't know if this is free of desync (I checked and there is not desync yet)
                        EndCinematicScene()
                        if self.played then
                            StopSound(self.played, false, true)
                        end
                    end
                end
                if IsForceEmpty(self.toForce) then
                    PauseTimer(self.t)
                    TimerStart(self.t, RMinBJ(bj_TRANSMISSION_PORT_HANGTIME, TimerGetRemaining(self.t)), false, callback)
                end
            end
            AllInstances[p] = {}
        end)
        -- --
        LocalPlayer = GetLocalPlayer()
        ForceCinematicSubtitles(true)
        -- --
        oldinit()
    end

end
v1.0
  • Released
v2.0
  • Added AddStep method with individual methods to edit the values.
  • Added Stop and Resume functions to the transmissions.
  • See the comment section to more information.
v2.0.1
  • Fixed some problems and member OriginalTargetForce added.
v2.0.2
  • Fixed some problems I didn't notice because I'm too distracted.
v2.1.0
  • Fixed AddAnotherStep, and now RemoveStep always reorder (I'm too lazy to recycle them).
v2.2.0
  • More polished.
  • Some methods changed its names for better ones (see the API) and the parameters of AddActions were interchanged.
  • Don't need Table or even use a hashtable, but yes or yes needs a library that requires Table XD (ArrayList).
v1.0
  • Released

Attachments

  • Transmission Lua.w3x
    200.8 KB · Views: 2
  • Transmission.w3x
    224.1 KB · Views: 6
Top