- Joined
- Jun 26, 2020
- Messages
- 1,928
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:
www.hiveworkshop.com
And also a test map with some examples.
The class is called
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.
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:
Remember once the transmission ends, its destroyed.
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()
Requirements:
- TimerUtils: [System] TimerUtilsEx
- Alloc: JASS/script.j at master · nestharus/JASS
- ArrayList: ArrayList
- (Optional) StoreUnitColor: StoreUnitColor
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:
Remember once the transmission ends, its destroyed.
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()
Requirements:
- Timed Call and Echo: [Lua] - Timed Call and Echo
- Definitive Doubly-Linked List: [Lua] - Definitive Doubly-Linked List
- Total Initialization: [Lua] - Total Initialization
- W3Types: [Lua] Debug Utils (Ingame Console etc.)
- (Optional) StoreUnitColor: StoreUnitColor
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)
Old Unit Transmission

And also a test map with some examples.
Attachments
Last edited: