• 🏆 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!

[vJass/Lua] Unit Transmission

Level 24
Joined
Jun 26, 2020
Messages
1,850
I made this system to make more easy create unit transmissions for people who wanna add them to their maps and to prevent them to use the infamous TriggerSleepAction that the BJ functions uses, also you can skip them without add "Skip remaining actions" for each dialogue line, so here it is:
The class is called Transmission.
If the sound is not stopping when you skip the transmission, check the fade out rate of the sound.
If the target force is not set, the default target force is all the in-game players.
When a player skips the transmission (or leaves the game) is removed from the target force.
vJASS:
function IsForceEmpty takes force f returns boolean
function ForceAddForce takes force principal, force other returns nothing
function ForceRemoveForce takes force principal, force other returns nothing
function Force takes player p returns force
vJASS:
player toPlayer // The player who is directed the transmission, if is a force, this would be the last player in the force
force toForce // The force who is directed the transmission
force OriginalTargetForce // The value toForce could be edited, and this stores the initial force anytime you use the function "SetTargetForce"
boolean isSkippable // Allows skip the transmission by pressing ESC (default true)

// Default values

unit DefUnit
integer DefUnitType
playercolor DefColor
string DefName
sound DefSound
string DefText
real DefDuration
integer DefTimeType
boolean DefWillWait
real DefDelay
vJASS:
// Asign the target force
method SetTargetForce takes force toForce returns nothing

// Adds an actions and a delay
// The returned value is from a class with the fields:
//    - real Delay
//    - trigger Actions
method AddActions takes real delay, code func returns TransmissionActions

// Add actions that you created manually
method AddCreatedActions takes TransmissionActions what returns TransmissionActions

// Add a line and asing manually its values
// The returned values is from a class with the fields:
//    - unit Unit
//    - integer UnitType
//    - playercolor Color
//    - string Name
//    - string Text
//    - integer TimeType: valid values Transmission.ADD, Transmission.SET, Transmission.SUB
//    - real Duration
//    - boolean WillWait
method AddLine takes unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait returns TransmissionLine

// The same as AddLine but there is no a unit, just unit-type
method AddLineById takes integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait returns TransmissionLine

// Add a line you created manually
method AddCreatedLine takes TransmissionLine what returns TransmissionLine

// Add a line and automatically and asing the default values you set before
method AddDefaultLine takes nothing returns TransmissionLine

// Adds action that will run when the transmission ends
// They will run even if the transmission was skipped
method AddEnd takes code func returns nothing

// Runs the transmission
method Start takes nothing returns nothing

// Pauses the transmission
// the boolean is to stop the sound with a fade out
method Pause takes boolean fadeout returns nothing

// Pauses the transmission and resume it after an asigned seconds
// the boolean is to stop the sound with a fade out
method PauseUntil takes boolean fadeout, real delay returns nothing

// Resumes the transmission
method Resume takes nothing returns nothing

// Returns if the transmission is paused
method IsPaused takes nothing returns boolean

// Returns an IntegerList that stores all the lines and actions in
// the order you set them
method GetElements takes nothing returns IntegerList

// Returns the current line or actions
method GetActualElement takes nothing returns TransmissionElements

// (Use it in the end callback)
// Returns if all the players in the target force skipped the transmission
// If was skipped in the last line or action it won't be counted as skipped
method WasSkipped takes nothing returns boolean

// Stores a value
method SetData takes integer data returns nothing

// Returns the stored data
method GetData takes nothing returns integer
vJASS:
// Creates the transmission
static method create takes nothing returns Transmission

// Creates the transmission and directly asign a target force and
// stores a value to use it later
static method createEx takes force toForce, integer data returns Transmission

// Pause all the started transmissions, ideal for time-stop events
// the boolean is to stop the sound with a fade out
static method PauseAll takes boolean fadeout returns nothing

// Resumes all the transmissions
static method ResumeAll takes nothing returns nothing

// Get the instance of the transmission during an action callback
static method GetCurrent takes nothing returns Transmission

// Creates and runs a transmission with just 1 line
static method Simple takes unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns Transmission
static method SimpleToForce takes force toForce, unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns Transmission
static method SimpleToPlayer takes player toPlayer, unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns Transmission
static method SimpleById takes integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns Transmission
static method SimpleToForceById takes force toForce, integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns Transmission
static method SimpleToPlayerById takes player toPlayer, integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns Transmission
Create the transmission, and its members:
vJASS:
local Transmission new = Transmission.create()
call new.AddActions(<Your delay>, <Your function>)
call new.AddLine(<Your unit>, <Your color>, <Your name>, <Your sound>, <Your text>, <How should be the time>, <Your time>, <If will wait>)
call new.AddEnd(<Your function>)
call new.Start()
Remember once the transmission ends, its destroyed.
Requirements:
vJASS:
library Transmission /*

    */requires /*
    */TimerUtils, /*
    */Alloc, /*
    */ArrayList, /*
    */optional StoreUnitColor
 
    // See API at https://www.hiveworkshop.com/threads/vjass-lua-unit-transmission.332814/
 
    // A functions to use
    globals
        private IntegerList array AllInstances
        private integer Temp
        private force array What_Force
        private force Other
        private force InGame
    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
 
        boolean isline
    
        method isLine takes nothing returns boolean
            return isline
        endmethod
    
        method isAction takes nothing returns boolean
            return not isline
        endmethod
    
        static method create takes nothing returns thistype
            return thistype.allocate()
        endmethod
    
        method destroy takes nothing returns nothing
            set isline = false
            call deallocate()
        endmethod
   
    endstruct
 
    struct TransmissionLine extends TransmissionElements
 
        unit Unit
        integer UnitType
        playercolor Color
        string Name
        real Duration
        integer TimeType
        boolean WillWait
        sound Sound
        string Text
   
        static method create takes nothing returns thistype
            local thistype this = TransmissionElements.create()
            set isline = true
            return this
        endmethod
    
        method destroy takes nothing returns nothing
            set Unit = null
            set UnitType = 0
            set Color = null
            set Name = null
            set Sound = null
            set Text = null
            set TimeType = -1
            set Duration = 0
            set WillWait = false
            call super.destroy()
        endmethod
 
    endstruct
 
    struct TransmissionActions extends TransmissionElements
    
        real Delay
        private trigger tActions
   
        static method create takes nothing returns thistype
            local thistype this = TransmissionElements.create()
            set isline = false
            return this
        endmethod
    
        method operator Actions= takes code func returns nothing
            // Since I can't store codes I use triggers
            if tActions != null then
                call TriggerClearActions(tActions)
                call DestroyTrigger(tActions)
            endif
            if func != null then
                set tActions = CreateTrigger()
                call TriggerAddAction(tActions, func)
            else
                set tActions = null
            endif
        endmethod
    
        method operator Actions takes nothing returns trigger
            return tActions
        endmethod
   
        method destroy takes nothing returns nothing
            set Delay = 0
            if tActions != null then
                call TriggerClearActions(tActions)
                call DestroyTrigger(tActions)
                set tActions = null
            endif
            call super.destroy()
        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 thistype instance
   
        readonly player toPlayer
        readonly force toForce
        readonly force OriginalTargetForce
    
        private IntegerList Steps
        private boolean allocated
   
        unit        DefUnit
        integer     DefUnitType
        playercolor DefColor
        string      DefName
        sound       DefSound
        string      DefText
        real        DefDuration
        integer     DefTimeType
        boolean     DefWillWait
        real        DefDelay
    
        boolean isSkippable
   
        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 boolean              ended
    
        private thistype next
        private thistype prev
   
        // 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 Steps.destroy()
            call ReleaseTimer(t)
            set ended = true
            set instance = this
            if final != null then
                call TriggerExecute(final)
                call TriggerClearActions(final)
                call DestroyTrigger(final)
                set final = null
            endif
            call ForForce(toForce, function thistype.ClearData)
            set instance = 0
            call DestroyForce(OriginalTargetForce)
            call DestroyForce(toForce)
            set elements = 0
            set data = 0
            set played = null
            set OriginalTargetForce = null
            set toForce = null
            set toPlayer = null
            set t = null
            set allocated = false
        
            set next.prev = prev
            set prev.next = next
        
            call deallocate()
        endmethod
   
        private method what_call takes nothing returns nothing
            set current = current + 1
            set elements = Steps[current]
            if elements == 0 or IsForceEmpty(toForce) then
                // If the cinematic was skipped just in the last line it won't be counted as skipped
                set skipped = IsForceEmpty(toForce) and elements != 0
                call finish()
            else
                if elements.isLine() then
                    call cinematic_line()
                else
                    call cinematic_actions()
                endif
            endif
        endmethod
   
        private static method callback takes nothing returns nothing
            call thistype(GetTimerData(GetExpiredTimer())).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 TransmissionLine what = elements
       
            set played = what.Sound
            set bj_lastTransmissionDuration = GetTransmissionDuration(played, what.TimeType, what.Duration)
       
            if IsPlayerInForce(thistype.LocalPlayer,toForce) then
                if played != null then
                    call StartSound(played)
                endif
                call SetCinematicScene(what.UnitType, what.Color, what.Name, what.Text, 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
       
            call TimerStart(t, delay, false, function thistype.callback)
       
            call what.destroy()
        endmethod
   
        private method cinematic_actions takes nothing returns nothing
            local TransmissionActions what = elements
       
            if what.Actions != null then
                set thistype.instance = this
                call TriggerExecute(what.Actions)
                set thistype.instance = 0
            endif
       
            call TimerStart(t, what.Delay, false, function thistype.callback)
            if paused then
                call PauseTimer(t) // In case you paused the transmission in an actions callback
            endif
       
            call what.destroy()
        endmethod
   
        private method Invalid takes string func returns boolean
            local boolean b = this <= 0 or not allocated
            static if DEBUG_MODE then
                if b then
                    call BJDebugMsg("Invalid instance |cffff0000"+func+"|r")
                endif
            endif
            return b
        endmethod
   
        // If the instance is 0 or a not allocated then none of this methods will run
   
        method SetData takes integer data returns nothing
            if Invalid("SetData") then
                return
            endif
            set this.data = data
        endmethod
   
        method GetData takes nothing returns integer
            if Invalid("GetData") then
                return 0
            endif
            return data
        endmethod
   
        method IsPaused takes nothing returns boolean
            if Invalid("IsPaused") then
                return false
            endif
            return paused
        endmethod
   
        method WasSkipped takes nothing returns boolean
            if Invalid("WasSkipped") then
                return false
            endif
            return skipped
        endmethod
    
        method GetElements takes nothing returns IntegerList
            if Invalid("GetElements") then
                return 0
            endif
            return Steps
        endmethod
    
        method GetActualElement takes nothing returns TransmissionElements
            if Invalid("GetActualElement") then
                return 0
            endif
            return elements
        endmethod
   
        method Resume takes nothing returns nothing
            if Invalid("Resume") then
                return
            endif
       
            set paused = false
            call ResumeTimer(t)
            /* This don't work correctly for some reason
            if played != null then
                if IsPlayerInForce(thistype.LocalPlayer, toForce) then
                    call StartSound(played)
                endif
                call SetSoundOffsetBJ(TimerGetElapsed(t), played) // In case of desync
            endif
            */
        endmethod
   
        method Pause takes boolean fadeout returns nothing
            if Invalid("Pause") then
                return
            endif
       
            set paused = true
            call PauseTimer(t)
            call StopSound(played, false, fadeout)
        endmethod
   
        private static method NowResume takes nothing returns nothing
            call thistype(GetTimerData(GetExpiredTimer())).Resume()
            call ReleaseTimer(GetExpiredTimer())
        endmethod
   
        method PauseUntil takes boolean fadeout, real delay returns nothing
            if Invalid("PauseUntil") then
                return
            endif
       
            call Pause(fadeout)
            if delay > 0.00 then
                call TimerStart(NewTimerEx(this), delay, false, function thistype.NowResume)
            else
                call Resume()
                debug call BJDebugMsg("Why do you even use this method?, Ay.")
            endif
        endmethod
   
        method Start takes nothing returns nothing
            if Invalid("Start") then
                return
            endif
            // The default value of toForce is all the in-game players
            if IsForceEmpty(toForce) then
                call ForceAddForce(toForce, InGame)
            endif
            // If there is not a line or action so it just go to the end
            set current = 0
            set elements = 0
            call what_call()
        endmethod
   
        // Since I can't store codes I use triggers
        method AddEnd takes code func returns nothing
            if Invalid("AddEnd") then
                return
            endif
       
            if final == null then
                set final = CreateTrigger()
            endif
            call TriggerAddAction(final, func)
        endmethod
   
       // Add a line and automatically and asing the default values you set before
        method AddDefaultLine takes nothing returns TransmissionLine
            local TransmissionLine what
            if Invalid("AddDefaultLine") then
                return 0
            endif
            set what = TransmissionLine.create()
        
            set what.Unit = DefUnit
            set what.UnitType = DefUnitType
            set what.Color = DefColor
            set what.Name = DefName
            set what.Sound = DefSound
            set what.Text = DefText
            set what.Duration = DefDuration
            set what.TimeType = DefTimeType
            set what.WillWait = DefWillWait
        
            call Steps.insert(what)
            return what
        endmethod
    
        // Add a line you created manually
        method AddCreatedLine takes TransmissionLine what returns TransmissionLine
            if Invalid("AddCreatedLine") then
                return 0
            endif
            call Steps.insert(what)
            return what
        endmethod
   
       // Add a line and asing manually its values
        method AddLine takes unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait returns TransmissionLine
            local TransmissionLine what
            if Invalid("AddLine") then
                return 0
            endif
        
            set what = TransmissionLine.create()
       
            set what.Unit = whichUnit
            set what.Name = unitName
            set what.Sound = soundHandle
            set what.Text = message
            set what.TimeType = timeType
            set what.Duration = timeVal
            set what.WillWait = wait
        
            if whichUnit == null then
                set what.UnitType = 0
                if whichColor == null then
                    static if LIBRARY_StoreUnitColor then
                        set what.Color = PLAYER_COLOR_BLACK
                    else
                        set what.Color = ConvertPlayerColor(PLAYER_NEUTRAL_AGGRESSIVE)
                    endif
                else
                    set what.Color = whichColor
                endif
            else
                set what.UnitType = GetUnitTypeId(whichUnit)
                if whichColor == null then
                    static if LIBRARY_StoreUnitColor then
                        set what.Color = GetUnitColor(whichUnit)
                    else
                        set what.Color = GetPlayerColor(GetOwningPlayer(whichUnit))
                    endif
                else
                    set what.Color = whichColor
                endif
            endif
            call Steps.insert(what)
            return what
        endmethod
   
       // The same as AddLine but there is no a unit, just unit-type
        method AddLineById takes integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait returns TransmissionLine
            local TransmissionLine what
            if Invalid("AddLineById") then
                return 0
            endif
        
            set what = 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 AddActions takes real delay, code func returns TransmissionActions
            local TransmissionActions what
            if Invalid("AddActions") then
                return 0
            endif
            set what = TransmissionActions.create()
            set what.Actions = func
            set what.Delay = delay
            call Steps.insert(what)
            return what
        endmethod
   
        // To add an action you created manually
        method AddCreatedActions takes TransmissionActions what returns TransmissionActions
            if Invalid("AddCreatedActions") then
                return 0
            endif
            call Steps.insert(what)
            return what
        endmethod
   
        private static method AddPlayer takes nothing returns nothing
            local thistype this = thistype.instance
            local player p = GetEnumPlayer()
            local integer i = GetPlayerId(p)
            set toPlayer = p
            call ForceAddPlayer(toForce, p)
            call ForceAddPlayer(OriginalTargetForce, p)
            call AllInstances[i].insert(this)
            set p = null
        endmethod
   
        // You can edit the target force
        method SetTargetForce takes force toForce returns nothing
            if 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
   
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
           
            set allocated = true
            set skipped = false
            set paused = false
            set toForce = CreateForce()
            set OriginalTargetForce = CreateForce()
            set t = NewTimerEx(this)
            set ended = false
            set Steps = IntegerList.create()
            set isSkippable = true
        
            set next = thistype(0).next
            set thistype(0).next.prev = this
            set thistype(0).next = this
            set prev = 0
       
            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
    
        // Pauses all the transmissions (ideal for time-stop events)
        static method PauseAll takes boolean fadeout returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                call this.Pause(fadeout)
                set this = this.next
            endloop
        endmethod
    
        // Resumes all the transmissions
        static method ResumeAll takes nothing returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                call this.Resume()
                set this = this.next
            endloop
        endmethod
    
        // Get the instance of the transmission during an action callback
        static method GetCurrent takes nothing returns thistype
            return instance
        endmethod
   
        // Methods to create and run just 1 line of transmission
    
        static method Simple takes unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns thistype
            local thistype this = thistype.createEx(InGame, 0)
            call this.AddLine(whichUnit, whichColor, unitName, soundHandle, message, timeType, timeVal, true)
            call this.Start()
            return this
        endmethod
    
        static method SimpleToForce takes force toForce, unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns thistype
            local thistype this = thistype.createEx(toForce, 0)
            call this.AddLine(whichUnit, whichColor, unitName, soundHandle, message, timeType, timeVal, true)
            call this.Start()
            return this
        endmethod
    
        static method SimpleToPlayer takes player toPlayer, unit whichUnit, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns thistype
            local thistype this = thistype.createEx(Force(toPlayer), 0)
            call this.AddLine(whichUnit, whichColor, unitName, soundHandle, message, timeType, timeVal, true)
            call this.Start()
            return this
        endmethod
    
        static method SimpleById takes integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns thistype
            local thistype this = thistype.createEx(InGame, 0)
            call this.AddLineById(id, whichColor, unitName, soundHandle, message, timeType, timeVal, true)
            call this.Start()
            return this
        endmethod
    
        static method SimpleToForceById takes force toForce, integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns thistype
            local thistype this = thistype.createEx(toForce, 0)
            call this.AddLineById(id, whichColor, unitName, soundHandle, message, timeType, timeVal, true)
            call this.Start()
            return this
        endmethod
    
        static method SimpleToPlayerById takes player toPlayer, integer id, playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal returns thistype
            local thistype this = thistype.createEx(Force(toPlayer), 0)
            call this.AddLineById(id, whichColor, unitName, soundHandle, message, timeType, timeVal, true)
            call this.Start()
            return this
        endmethod
   
        // If a player skips cinematic he will be removed from every instance trasnmission where is in
        private 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 this.isSkippable and GetTriggerEventId() != EVENT_PLAYER_LEAVE 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
                        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
                        call AllInstances[i].removeFrom(j)
                        set j = j - 1
                    endif
                endif
           
                set j = j + 1
            endloop
            set p = null
        
            if GetTriggerEventId() == EVENT_PLAYER_LEAVE then
                call ForceRemovePlayer(InGame, GetTriggerPlayer())
            endif
        endmethod
    
        private static method InGamePlayers takes nothing returns nothing
            local player p
            local integer i = 0
            set InGame = CreateForce()
            loop
                exitwhen i > PLAYER_NEUTRAL_AGGRESSIVE
                set p = Player(i)
                if GetPlayerController(p) == MAP_CONTROL_USER and GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING then
                    call ForceAddPlayer(InGame, p)
                endif
                set i = i + 1
            endloop
            set p = null
        endmethod
   
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local integer i = 0
            local player p
            call TimerStart(NewTimer(), 0., false, function thistype.InGamePlayers)
            loop
                exitwhen i > PLAYER_NEUTRAL_AGGRESSIVE
                set p = Player(i)
                set What_Force[i] = GetForceOfPlayer(p) // To use it if you wanna send the transmission just for 1 player
                set AllInstances[i] = IntegerList.create()
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_END_CINEMATIC)
                call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_LEAVE)
                set i = i + 1
            endloop
            call TriggerAddAction(t, function thistype.onActions)
            //-- --
            set thistype.LocalPlayer = GetLocalPlayer()
            call ForceCinematicSubtitles(true)
            set p = null
        endmethod
    endmodule
 
    struct Transmission extends array
        implement Alloc
        implement M
    endstruct
 
endlibrary
Lua:
player toPlayer -- The player who is directed the transmission, if is a force, this would be the last player in the force
force toForce -- The force who is directed the transmission
force OriginalTargetForce -- The value toForce could be edited, and this stores the initial force anytime you use the function "SetTargetForce"
boolean isSkippable -- Allows skip the transmission by pressing ESC (default true)

-- Default values

unit DefUnit
integer DefUnitType
playercolor DefColor
string DefName
sound DefSound
string DefText
real DefDuration
integer DefTimeType
boolean DefWillWait
real DefDelay
Lua:
-- Asign the target force
SetTargetForce(force toForce)

-- Adds an actions, you can just add a delay or an actions
-- The returned value is a table with the fields:
--    - real Delay
--    - function Actions
-- The first parameter of the passed function reffers to the actual transmission
AddActions([real delay][, function func]) returns table

-- Adds a line, the expected values are:
--  - (unit whichUnit or integer unittype), playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait
--  - table line
--  - nothing
-- In case of the table, it should have the fields:
--    - unit Unit
--    - integer UnitType (nil value 0)
--    - playercolor Color
--    - string Name (nil value "")
--    - string Text (nil value "")
--    - integer TimeType : valid values Transmission.ADD, Transmission.SET, Transmission.SUB (nil value -1)
--    - real Duration (nil value 0)
--    - boolean WillWait (nil value false)
-- In case you didn't add a parameter, the default values will be asign.
-- The returned value is a table with the fields previously explained
AddLine(...) returns table

-- Adds action that will run when the transmission ends
-- They will run even if the transmission was skipped
-- The first parameter of the passed function reffers to the actual transmission
AddEnd(function func)

-- Runs the transmission
Start()

-- Pauses the transmission and maybe resume it after an asigned seconds
-- the boolean is to stop the sound with a fade out
Pause(boolean fadeOut[, real delay])

-- Resumes the transmission
Resume()

-- Returns if the transmission is paused
IsPaused() returns boolean

-- Returns an array (table) that stores all the lines and actions in
-- the order you set them
GetElements() returns table

-- Returns the current line or actions
GetActualElement() returns table

-- (Use it in the end callback)
-- Returns if all the players in the target force skipped the transmission
-- If was skipped in the last line or action it won't be counted as skipped
WasSkipped() returns boolean

-- Stores a value
SetData(any data)

-- Returns the stored data
GetData() returns any
Lua:
-- Creates the transmission and directly asign a target force and
-- maybe store a value to use it later
function Transmission.create([force toForce[, any data]]) returns Transmission

-- Pause all the started transmissions, ideal for time-stop events
-- the boolean is to stop the sound with a fade out
function Transmission.PauseAll(boolean fadeOut)

-- Resume all the paused transmissions
function Transmission.ResumeAll()

-- Creates and runs a transmission with just 1 line, the expected values are:
-- [player toPlayer or force toForce], (unit whichUnit or integer unittype), playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, real timeVal
function Transmission.Simple(...) returns Transmission
Create the transmission, and its members:
Lua:
local new = Transmission.create()
new:AddActions(<Your delay>)
new:AddActions(<Your function>)
new:AddLine(<Your unit>, <Your color>, <Your name>, <Your sound>, <Your text>, <How should be the time>, <Your time>, <If will wait>)
new:AddEnd(<Your function>)
new:Start()
Remember once the transmission ends, its destroyed.
Requirements:
Lua:
OnInit("Transmission", function ()
    Require "LinkedList"
    Require "Timed"
    Require "Wc3Type"
    local StoreUnitColor = Require.optional "StoreUnitColor"

    -- See the API here https://www.hiveworkshop.com/threads/vjass-lua-unit-transmission.332814/

    -- A functions to use
    local AllInstances = {} ---@type table<player, Transmission[]>
    local WhatForce = {} ---@type table<player, force>
    local InGame = nil ---@type force
    local LocalPlayer = GetLocalPlayer()

    ---@param force force
    ---@return boolean
    function IsForceEmpty(force)
        if force then
            local count = 0
            ForForce(force, function ()
                count = count + 1
            end)
            return count == 0
        end
        return true
    end

    ---@param principal force
    ---@param other force
    function ForceAddForce(principal, other)
        if principal and other then
            ForForce(other, function ()
                ForceAddPlayer(principal, GetEnumPlayer())
            end)
        end
    end

    ---@param principal force
    ---@param other force
    function ForceRemoveForce(principal, other)
        if principal and other then
            ForForce(other, function ()
                ForceRemovePlayer(principal, GetEnumPlayer())
            end)
        end
    end

    ---@param player player
    ---@return force
    function Force(player)
        return WhatForce[player]
    end

    -- To have compatibility with Reforged
    local getType = BlzGetUnitSkin or GetUnitTypeId

    ---@class Transmission
    ---@field toPlayer player
    ---@field toForce force
    ---@field OriginalTargetForce force
    ---@field isSkippable boolean
    ---@field DefUnit unit
    ---@field DefUnitType integer
    ---@field DefColor playercolor
    ---@field DefName string
    ---@field DefSound sound
    ---@field DefText string
    ---@field DefTimeType integer
    ---@field DefDuration number
    ---@field DefWillWait boolean
    Transmission = {}
    Transmission.__index = Transmission

    local All = LinkedList.create()

    -- These are to make it more elegant than just put integers, but you can use the integers if you want
    Transmission.ADD = bj_TIMETYPE_ADD  -- 0
    Transmission.SET = bj_TIMETYPE_SET  -- 1
    Transmission.SUB = bj_TIMETYPE_SUB  -- 2

    -- Where the magic happens

    function Transmission:_finish()
        self._Steps = nil
        self._ended = true
        DestroyTimer(self._t)
        if self._final then
            self._final(self)
            self._final = nil
        end
        ForForce(self.toForce, function ()
            local list = AllInstances[GetEnumPlayer()]
            for i, v in ipairs(list) do
                if v == self then
                    table.remove(list, i)
                    break
                end
            end
        end)
        DestroyForce(self.toForce)
        DestroyForce(self.OriginalTargetForce)
        self._elements = nil
        self._played = nil
        self.toForce = nil
        self.OriginalTargetForce = nil
        self._t = nil
        self:remove()
    end

    function Transmission:_what_call()
        self._current = self._current + 1
        self._elements = self._Steps[self._current]
        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 ~= nil
            self:_finish()
        else
            if self._elements._isline then
                self:_cinematic_line()
            else
                self:_cinematic_actions()
            end
        end
    end

    function Transmission:_cinematic_line()
        local alpha = 0
        local delay = 0
        local what = self._elements

        self._played = what.Sound
        local duration = GetTransmissionDuration(self._played, what.TimeType, what.Duration)
        what.UnitType = what.UnitType or getType(what.Unit)
        if IsPlayerInForce(LocalPlayer, self.toForce) then
            if self._played ~= nil then
                StartSound(self._played)
            end
            SetCinematicScene(what.UnitType, what.Color, what.Name, what.Text, duration + bj_TRANSMISSION_PORT_HANGTIME, duration)
            alpha = 255
        end
        if what.Unit then
            UnitAddIndicator(what.Unit, 255, 255, 255, alpha)
        end

        if what.WillWait then
            delay = duration
        end

        TimerStart(self._t, delay, false, function () self:_what_call() end)
    end

    function Transmission:_cinematic_actions()
        local what = self._elements

        if what.Actions then
            what.Actions(self)
        end

        TimerStart(self._t, what.Delay, false, function () self:_what_call() end)
        if self._paused then
            PauseTimer(self._t) -- In case you paused the transmission in an actions callback
        end
    end

    ---Stores a value
    ---@param data any
    function Transmission:SetData(data)
        self._data = data
    end

    ---Returns the stored data
    ---@return any
    function Transmission:GetData()
        return self._data
    end

    ---Returns if the transmission is paused
    ---@return boolean
    function Transmission:IsPaused()
        return self._paused
    end

    ---(Use it in the end callback).
    ---Returns if all the players in the target force skipped the transmission.
    ---If was skipped in the last line or action it won't be counted as skipped
    ---@return boolean
    function Transmission:WasSkipped()
        return self._skipped
    end

    ---Returns an array (table) that stores all the lines and actions in
    ---the order you set them
    ---@return table<integer, table>
    function Transmission:GetElements()
        return self._Steps
    end

    ---Returns the current line or actions
    ---@return table
    function Transmission:GetActualElement()
        return self._elements
    end

    ---Resumes the transmission
    function Transmission:Resume()
        self._paused = false
        ResumeTimer(self._t)
    end

    ---Pauses the transmission and maybe resume it after an asigned seconds
    ---the boolean is to stop the sound with a fade out
    ---@param fadeOut boolean
    ---@param delay? number
    function Transmission:Pause(fadeOut, delay)
        self._paused = true
        PauseTimer(self._t)
        StopSound(self._played, false, fadeOut)
        if delay then
            Timed.call(delay, function () self:Resume() end)
        end
    end

    ---Runs the transmission
    function Transmission:Start()
        -- The default force is all the in-game players
        if IsForceEmpty(self.toForce) then
            self:SetTargetForce(InGame)
        end
        self._current = 0
        self._elements = nil
        self:_what_call()
    end

    ---Adds actions that will run when the transmission ends.
    ---They will run even if the transmission was skipped.
    ---@param func fun(t?: Transmission)
    function Transmission:AddEnd(func)
        -- I can store functions :D
        self._final = func
    end

    ---Adds a line, the expected values are:
    --- - `(unit whichUnit or integer unittype), playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, number timeVal, boolean wait`
    --- - `table line`
    --- - nothing
    ---
    ---In case of the table, it should have the fields:
    ---   - `unit Unit`
    ---   - `integer UnitType` (if the value is `nil` then it will be automatically detected per line)
    ---   - `playercolor Color`
    ---   - `string Name` (`nil` value `""`)
    ---   - `string Text` (`nil` value `""`)
    ---   - `integer TimeType` : valid values `Transmission.ADD, Transmission.SET, Transmission.SUB` (`nil` value `-1`)
    ---   - `number Duration` (`nil` value 0)
    ---   - `boolean WillWait` (`nil` value false)
    ---
    ---In case you didn't add a parameter, the default values will be asign.
    ---
    ---The returned value is a table with the fields previously explained
    ---@return table
    function Transmission:AddLine(...)
        local line = nil
        if ... then
            local args = {...}
            if type(args[1]) == "table" then
                line = args[1]
                -- In case the passed table has this values as nil
                line.Name = line.Name or ""
                line.Text = line.Text or ""
                line.TimeType = line.TimeType or -1
                line.Duration = line.Duration or 0
                line.WillWait = line.WillWait or false
            else
                line = {
                    Color = args[2],
                    Name = args[3] or "",
                    Sound = args[4],
                    Text = args[5] or "",
                    TimeType = args[6] or -1,
                    Duration = args[7] or 0,
                    WillWait = args[8] or false
                }
                local unit = args[1]
                local typ = Wc3Type(unit)
                if typ == "unit" or typ == "nil" then
                    line.Unit = unit
                    if not unit then
                        if not line.Color then
                            if StoreUnitColor then
                                line.Color = PLAYER_COLOR_BLACK
                            else
                                line.Color = ConvertPlayerColor(PLAYER_NEUTRAL_AGGRESSIVE)
                            end
                        end
                    else
                        if not line.Color then
                            if StoreUnitColor then
                                line.Color = GetUnitColor(line.Unit)
                            else
                                line.Color = GetPlayerColor(GetOwningPlayer(line.Unit))
                            end
                        end
                    end
                elseif typ == "integer" then
                    line.Unit = nil
                    line.UnitType = unit
                    if not line.Color then
                        if StoreUnitColor then
                            line.Color = PLAYER_COLOR_BLACK
                        else
                            line.Color = ConvertPlayerColor(PLAYER_NEUTRAL_AGGRESSIVE)
                        end
                    end
                else
                    error("Invalid first argument", 2)
                end
            end
        else
            line = {
                Unit = self.DefUnit,
                UnitType = self.DefUnitType or 0,
                Color = self.DefColor,
                Name = self.DefName or "",
                Sound = self.DefSound,
                Text = self.DefText or "",
                TimeType = self.DefTimeType or -1,
                Duration = self.DefDuration or 0,
                WillWait = self.DefWillWait or false
            }
        end
        line._isline = true
        table.insert(self._Steps, line)
        return line
    end

    ---Adds an actions, you can just add a delay or an actions.
    ---The returned value is a table with the fields:
    ---   - `number` Delay
    ---   - `fun(t: Transmission)` Actions
    ---@param delay number|fun(t?: Transmission)
    ---@param func? fun(t?: Transmission)
    ---@return table
    function Transmission:AddActions(delay, func)
        local actions = nil
        local typ = type(delay)
        if typ == "table" then
            actions = delay
        elseif typ ~= "nil" then
            if typ == "function" then
                func, delay = delay, 0
            end
            actions = {
                Delay = delay,
                Actions = func
            }
        else
            error("You are entering a nil value", 2)
        end
        actions._isline = false
        table.insert(self._Steps, actions)
        return actions
    end

    ---Asign the target force
    ---@param toForce force
    function Transmission:SetTargetForce(toForce)
        if toForce and not IsForceEmpty(toForce) then
            ForForce(toForce, function()
                local player = GetEnumPlayer()
                self.toPlayer = player
                ForceAddPlayer(self.toForce, player)
                ForceAddPlayer(self.OriginalTargetForce, player)
                table.insert(AllInstances[player], self)
            end)
        end
    end

    ---Creates the transmission and directly asign a target force and
    ---maybe store a value to use it later
    ---@param toForce? force
    ---@param data? any
    ---@return Transmission
    function Transmission.create(toForce, data)
        local self = All:insert()
        setmetatable(self, Transmission) -- Sorry, but I don't wanna have LinkedList as its metatable

        self._skipped = false
        self._paused = false
        self._played = nil
        self.toForce = CreateForce()
        self.OriginalTargetForce = CreateForce()
        self._t = CreateTimer()
        self._ended = false
        self._Steps = {}
        self.isSkippable = true
        if toForce then
            self:SetTargetForce(toForce)
        end
        self._data = data

        return self
    end

    ---Pause all the started transmissions, ideal for time-stop events
    ---the boolean is to stop the sound with a fade out
    ---@param fadeOut? boolean
    function Transmission.PauseAll(fadeOut)
        for node in All:loop() do
            node:Pause(fadeOut)
        end
    end

    ---Resume all the paused transmissions
    function Transmission.ResumeAll()
        for node in All:loop() do
            node:Resume()
        end
    end

    ---Creates and runs a transmission with just 1 line, the expected values are:
    ---
    ---`[player toPlayer or force toForce], (unit whichUnit or integer unittype), playercolor whichColor, string unitName, sound soundHandle, string message, integer timeType, number timeVal`
    ---@return Transmission?
    function Transmission.Simple(...)
        local new = nil
        local args = {...}
        if #args == 7 then
            new = Transmission.create(InGame)
            table.insert(args, true)
            new:AddLine(table.unpack(args))
        elseif #args == 8 then
            local typ = Wc3Type(args[1])
            if typ == "force" then
                new = Transmission.create(args[1])
            elseif typ == "player" then
                new = Transmission.create(Force(args[1]))
            else
                error("Invalid first argument", 2)
            end
            table.insert(args, true)
            new:AddLine(table.unpack(args, 2, 9))
        else
            error("Invalid number of arguments", 2)
        end
        new:Start()
        return new
    end

    Timed.call(function ()
        InGame = CreateForce()
        for i = 0, PLAYER_NEUTRAL_AGGRESSIVE do
            local player = Player(i)
            if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
                ForceAddPlayer(InGame, player)
            end
        end
    end)

    local t = CreateTrigger()

    for i = 0, PLAYER_NEUTRAL_AGGRESSIVE do
        local player = Player(i)
        WhatForce[player] = GetForceOfPlayer(player)
        AllInstances[player] = {}
        TriggerRegisterPlayerEvent(t, player, EVENT_PLAYER_END_CINEMATIC)
        TriggerRegisterPlayerEvent(t, player, EVENT_PLAYER_LEAVE)
    end
    TriggerAddAction(t, function ()
        local player = GetTriggerPlayer()
        for i = #AllInstances[player], 1, -1 do
            local curr = AllInstances[player][i]
            if curr.isSkippable and GetTriggerEventId() ~= EVENT_PLAYER_LEAVE then
                ForceRemovePlayer(curr.toForce, player)
                if player == LocalPlayer then
                    -- I don't know if this is free of desync (I checked and there is not desync yet)
                    EndCinematicScene()
                    if curr._played then
                        StopSound(curr._played, false, true)
                    end
                end
                if IsForceEmpty(curr.toForce) then
                    PauseTimer(curr._t)
                    TimerStart(curr._t, RMinBJ(bj_TRANSMISSION_PORT_HANGTIME, TimerGetRemaining(curr._t)), false, function () curr:_what_call() end)
                end
                table.remove(AllInstances[player], i)
            end
        end

        if GetTriggerEventId() == EVENT_PLAYER_LEAVE then
            ForceRemovePlayer(InGame, player)
        end
    end)
    -- --
    ForceCinematicSubtitles(true)

end)

And also a test map with some examples.
 

Attachments

  • Unit Transmission vJass Redux.w3x
    82 KB · Views: 4
  • Unit Transmission Lua Redux.w3m
    85.8 KB · Views: 0
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,850
I did a little changes in the code to fix things and I replaced the TriggerAddConditions for TriggerAddActions because I had to change Editor to use the multi-instance and it doesn't valid the Condition with functions those returns nothing, I don't edit the main code yet because I don't know if this will be the last code:
vJASS:
library Transmission requires /*
    
    */TimerUtils, /*
    */Alloc, /*
    */optional Table
    
    //! novjass
    //How it works:
    //    Set a value of type Transmission and use the function create like this:
        
            local Transmission curr=Transmission.create(toForce,data)
            
     //   "toForce" is the force that will see and hear the transmission, "data" is an integer that you can store to use during and at the end of the transmission.
        
     //   Then to add a dialogue line just do:
        
            call curr.AddLine(unit whichUnit,string unitName,sound soundHandle,string message,integer timeType,real timeVal,boolean wait,code actions)
            
        "whichUnit": The unit who do the dialogue line.
        "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.
        "actions": If you wanna add an action that happens when the dialogue line is happening.
        
    //    You can also add an action to the end of the transmission using
    
            call curr.AddEnd(code func)
            
    /*    Where you can use the values */Transmission.Data/* if you stored one and */Transmission.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(real dalay)
            
    //    To start the transmission (and delayed if you wanna), and to skip the transmission 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. 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).*/
    //! endnovjass
    
    //A function to use
    globals
        private integer Temp
        private force array What_Force
    endglobals
    
    private function Enum takes nothing returns nothing
        set Temp=Temp+1
    endfunction
    
    private 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
    
    //This are the elements of each line transmission
    
    private struct Elements extends array
    
        implement Alloc
        unit talker
        playercolor color
        string name
        real duration
        integer timetype
        boolean wait
        sound mysound
        string text
        trigger actions
        
        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
            call TriggerClearConditions(this.actions)
            call DestroyTrigger(this.actions)
            set this.actions=null
            call this.deallocate()
        endmethod
        
        static method create takes unit whichUnit, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait, code func returns thistype
            local thistype this=thistype.allocate()
            
            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
            //Since I can't store codes I use triggers
            if func!=null then
                set this.actions=CreateTrigger()
                call TriggerAddAction(this.actions,func)
            endif
            
            return this
        endmethod
        
    endstruct
    
    //A methods to have a linked list to each instance transmission
    
    private struct Next extends array
        
        static if LIBRARY_Table then
            static TableArray n
        else
            static hashtable n=InitHashtable()
        endif
        
        method operator []= takes integer k, integer v returns nothing
            static if LIBRARY_Table then
                set thistype.n[this].integer[k]=v
            else
                call SaveInteger(thistype.n,this,k,v)
            endif
        endmethod
        
        method operator [] takes integer k returns integer
            static if LIBRARY_Table then
                return thistype.n[this].integer[k]
            else
                return LoadInteger(thistype.n,this,k)
            endif
        endmethod
        
    endstruct
    
    private struct Prev extends array
    
        static if LIBRARY_Table then
            static TableArray p
        else
            static hashtable p=InitHashtable()
        endif
        
        method operator []= takes integer k, integer v returns nothing
            static if LIBRARY_Table then
                set thistype.p[this].integer[k]=v
            else
                call SaveInteger(thistype.p,this,k,v)
            endif
        endmethod
        
        method operator [] takes integer k returns integer
            static if LIBRARY_Table then
                return thistype.p[this].integer[k]
            else
                return LoadInteger(thistype.p,this,k)
            endif
        endmethod
        
    endstruct
    
    //The actual struct of the transmission
    
    private module M
        
        //These are to make it more elegant than just put integers
        readonly static constant integer ADD=0
        readonly static constant integer SET=1
        readonly static constant integer SUB=2
        
        readonly static integer Data
        readonly static boolean Skipped
        readonly static player LocalPlayer
        private static integer array Index
        private static integer array Index_current
        
        static if LIBRARY_Table then
            private static TableArray Instance
        else
            private static hashtable Instance=InitHashtable()
        endif
        
        //To store a temp "this" to pass it to another functions
        private static thistype temp
        
        Elements elements
        private integer data
        readonly force toForce
        private trigger final
        private sound played
        private timer t
        
        method operator next takes nothing returns Next
            return this
        endmethod
        
        method operator prev takes nothing returns Prev
            return this
        endmethod
        
        private static method ClearData takes nothing returns nothing
            local integer i=GetPlayerId(GetEnumPlayer())
            set thistype.Index_current[i]=thistype.Index_current[i]-1
            if thistype.Index_current[i]==0 then
                set thistype.Index[i]=0
                static if LIBRARY_Table then
                    call thistype.Instance[i].flush()
                else
                    call FlushChildHashtable(thistype.Instance,i)
                endif
            endif
        endmethod
        
        //When the transmission ends
        private method finish takes nothing returns nothing
            set this.next[0]=0
            set this.prev[0]=0
            call ReleaseTimer(this.t)
            call ForForce(this.toForce,function thistype.ClearData)
            call DestroyForce(this.toForce)
            if this.final!=null then
                set thistype.Data=this.data
                call TriggerExecute(this.final)
                call TriggerClearConditions(this.final)
                call DestroyTrigger(this.final)
                set this.final=null
                set thistype.Data=0
            endif
            set thistype.Skipped=false
            set this.played=null
            set this.toForce=null
            set this.t=null
            call this.deallocate()
        endmethod
        
        private static method callback takes nothing returns nothing
            local thistype this=GetTimerData(GetExpiredTimer())
            if this.elements==0 or IsForceEmpty(this.toForce) then
                if IsForceEmpty(this.toForce) and this.elements!=0 then
                //If the cinematic was skipped just in the last line it won't be counted as skipped
                    set thistype.Skipped=true
                endif
                call this.finish()
            else
                call this.cinematic_line()
            endif
        endmethod
        
        //Where the magic happens
        private method cinematic_line takes nothing returns nothing
            local Elements what=this.elements
            local integer alpha=0
            local real delay=0.00
            
            set bj_lastTransmissionDuration=GetTransmissionDuration(what.mysound,what.timetype,what.duration)
            
            if IsPlayerInForce(thistype.LocalPlayer,this.toForce) then
                if what.mysound!=null then
                    set this.played=what.mysound
                    call StartSound(this.played)
                endif
                call SetCinematicScene(GetUnitTypeId(what.talker),what.color,what.name,what.text,bj_lastTransmissionDuration+bj_TRANSMISSION_PORT_HANGTIME,bj_lastTransmissionDuration)
                set alpha=255
            endif
            call UnitAddIndicator(what.talker,255,255,255,alpha)
            
            if what.actions!=null then
                set thistype.Data=this.data
                call TriggerExecute(what.actions)
                set thistype.Data=0
            endif
            
            set this.elements=this.next[this.elements]
            
            if what.wait then
                set delay=bj_lastTransmissionDuration
            endif
            
            if delay>0.00 then
                call TimerStart(this.t,delay,false,function thistype.callback)
            else
                call thistype.callback()
            endif
            
            call what.destroy()
        endmethod
        
        //If the instance is 0 then none of this methods will run
        
        method Start takes real delay returns nothing
            //If there is not a line so it just go to the end
            if this!=0 then
                set this.elements=this.next[0]
                if delay>0.00 then
                    call TimerStart(this.t,delay,false,function thistype.callback)
                else
                    call this.cinematic_line()
                endif
            endif
        endmethod
        
        //Since I can't store codes I use triggers
        method AddEnd takes code func returns nothing
            if this!=0 then
                if this.final==null then
                    set this.final=CreateTrigger()
                endif
                call TriggerAddAction(this.final,func)
            endif
        endmethod
        
        //To set a line with all the needed values
        method AddLine takes unit whichUnit, string unitName, sound soundHandle, string message, integer timeType, real timeVal, boolean wait, code actions returns nothing
            local Elements what
            if this!=0 then
                set what=Elements.create(whichUnit,unitName,soundHandle,message,timeType,timeVal,wait,actions)
        
                //Add to list
                set this.next[what]=0
                set this.prev[what]=this.prev[0]
                set this.next[this.prev[what]]=what
                set this.prev[0]=what
            endif
        
        endmethod
        
        private static method AddPlayer takes nothing returns nothing
            local thistype this=thistype.temp
            local player p=GetEnumPlayer()
            local integer i=GetPlayerId(p)
            call ForceAddPlayer(this.toForce,p)
            static if LIBRARY_Table then
                set thistype.Instance[i].integer[thistype.Index[i]]=this
            else
                call SaveInteger(thistype.Instance,i,thistype.Index[i],this)
            endif
            set thistype.Index[i]=thistype.Index[i]+1
            set thistype.Index_current[i]=thistype.Index_current[i]+1
            set p=null
        endmethod
        
        static method create takes force toForce, integer data returns thistype
            local thistype this
            //Only returns a valid value if the force is valid
            if toForce!=null and not IsForceEmpty(toForce) then
                set this=thistype.allocate()
                
                set this.toForce=CreateForce()
                set thistype.temp=this
                call ForForce(toForce,function thistype.AddPlayer)
                set this.t=NewTimerEx(this)
                
                set this.data=data
                
                return this
            endif
            return 0
        endmethod
        
        //If a player skips cinematic he will be removed from every instance trasnmission where is in
        static method onActions takes nothing returns nothing
            local thistype this
            local player p=GetTriggerPlayer()
            local integer i=GetPlayerId(p)
            local integer j=0
            loop
                exitwhen j>thistype.Index[i]
                static if LIBRARY_Table then
                    set this=thistype.Instance[i].integer[j]
                else
                    set this=LoadInteger(thistype.Instance,i,j)
                endif
                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
                
                set j=j+1
            endloop
            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
                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)
            //-- --
            static if LIBRARY_Table then
                set Next.n=TableArray[0x2000]
                set Prev.p=TableArray[0x2000]
                set thistype.Instance=TableArray[0x2000]
            endif
        endmethod
    endmodule
    
    struct Transmission extends array
        implement Alloc
        implement M
    endstruct
    
    function Force takes player p returns force
        return What_Force[GetPlayerId(p)]
    endfunction
    
endlibrary
Also I added sounds to the cinematic to test better.
 

Attachments

  • Transmission.w3x
    50.3 KB · Views: 20

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
JASS:
            integer NumberLine //Please don't edit this value
            integer NumberAction //Please don't edit this value
            integer NumberStep //Please don't edit this value
You can use the readonly keyword to prevent people from editing this.

You should have some clearer documentation or naming system. It's not always clear whether a function refers to the current step, a specific step or no step at all. For example here the documentation makes it sound like it affects everything:
JASS:
            method SetActions takes code func returns nothing  //This replace all the actions
            method AddMoreActions takes code func returns nothing //This add action without affecting the added actions
            method GetActions takes nothing returns trigger


JASS:
call curr.AddEnd(code func)
Is it possible to distinguish between a naturally ending cinematic and a skipped one? If not you should probably add a way to distinguish them.

JASS:
call curr.SetData(integer data) /*To recover the data use:*/ curr.Data
If you can access Data directly with curr.Data, I see no reason why you shouldn't set it with curr.Data = .
 
Level 24
Joined
Jun 26, 2020
Messages
1,850
@Jampion I made this code when I was very noob in this of advanced coding, I think, yes, this code have a lot of things that I should change
You can use the readonly keyword to prevent people from editing this.
Yes, I don't know why I didn't do that before.
You should have some clearer documentation or naming system. It's not always clear whether a function refers to the current step, a specific step or no step at all. For example here the documentation makes it sound like it affects everything:
What do you reffer?
Is it possible to distinguish between a naturally ending cinematic and a skipped one? If not you should probably add a way to distinguish them.
Yes, if a player press ESC is removed from the force of all cinematics are addressed to him, and if the target force of a cinematic is empty and is not in the final step there is member called "skipped", but I also don't know why is private (What was I thinking?)
If you can access Data directly with curr.Data, I see no reason why you shouldn't set it with curr.Data = .
(Again, what was I thinking?)
 
Level 24
Joined
Jun 26, 2020
Messages
1,850
JASS:
integer NumberLine //Please don't edit this value
integer NumberAction //Please don't edit this value
integer NumberStep //Please don't edit this value
You can use the readonly keyword to prevent people from editing this.
Ah I remembered why I didn't put them readonly, because I read-write them between 2 structs.
Is it possible to distinguish between a naturally ending cinematic and a skipped one? If not you should probably add a way to distinguish them.
I forgot this operator:
JASS:
method operator Skipped takes nothing returns boolean
    call this.Invalid("Skipped")
    return this.skipped
endmethod
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,850
Oops, I made a mistake in the Lua version:
Lua:
local player = Player(i)
for i = 0, PLAYER_NEUTRAL_AGGRESSIVE do
    if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
        ForceAddPlayer(InGame, player)
    end
end
Should be:
Lua:
for i = 0, PLAYER_NEUTRAL_AGGRESSIVE do
    local player = Player(i)
    if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
        ForceAddPlayer(InGame, player)
    end
end
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I'm planning update the system to add compatibility with the units skins functions added in Reforged, for Lua would be easy, but how could I do it for Jass?
vJass backwards-compatibility is a pain. In a strict coding environment (non-GUI), most things that you can do in Lua can be done in vJass (but with far more lines of code and far heavier operations) to make them work.

I recommend for resources that require 1.32+ to just not bother with vJass support. It's my opinion that vJass should only be developed at this point for people who are on old versions of WarCraft 3.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Hi Herly, is there anything further you wanted to do with this resource before I begin my review of it? One thing I have noticed is that you are outright overwriting some of the Force natives, maybe for efficiency, but this is redundant as if the user wants to clean up functions like that then they can use Lua-Infused GUI.

This can also definitely benefit from the Total Initialization "Require" API, so that the user doesn't have to worry about the order the code is implemented in.
 
Level 24
Joined
Jun 26, 2020
Messages
1,850
Hi Herly, is there anything further you wanted to do with this resource before I begin my review of it? One thing I have noticed is that you are outright overwriting some of the Force natives, maybe for efficiency, but this is redundant as if the user wants to clean up functions like that then they can use Lua-Infused GUI.

This can also definitely benefit from the Total Initialization "Require" API, so that the user doesn't have to worry about the order the code is implemented in.
In fact, there are things I wanna include, and those Force functions I include don't exist in the W3 API, that's why I created them.
 
Top