- Joined
- Feb 22, 2025
- Messages
- 315
So I ran into a problem while working on a co-op cinematic setup.
I needed:
From what I found on Hive, most cinematic setups still rely on Waits, and I couldn’t really find something that cleanly handles skipping in multiplayer scenarios.
So I ended up trying something different:
But at the same time… it feels kind of diabolical
So I wanted to ask:
Any feedback or alternative approaches would be really appreciated.
So this system is supposed to replace inaccurate waits and timing that I’m still struggling to achieve in the original trigger with waits as shown below:
I needed:
- reliable timing (no Wait issues)
- multiplayer-safe behavior
- and a proper skip system (ideally with voting, not just one player skipping)
From what I found on Hive, most cinematic setups still rely on Waits, and I couldn’t really find something that cleanly handles skipping in multiplayer scenarios.
So I ended up trying something different:
JASS:
//===========================================================================
// ESC key press detection and voting start
//===========================================================================
function Cinematic_OnEscPress takes nothing returns boolean
local player p = GetTriggerPlayer()
local integer pid = GetPlayerId(p)
// Only act if cinematic is running and voting not already active
if udg_Cinematic_Running and not udg_Skip_VotingActive then
// Show dialog for this player
if pid == 0 then
call DialogDisplay(p, udg_Skip_Dialog1, true)
elseif pid == 1 then
call DialogDisplay(p, udg_Skip_Dialog2, true)
endif
set udg_Skip_VotingActive = true
// Start a timeout timer (e.g., 10 seconds) to auto‑reset votes
call TimerStart(udg_Skip_VoteTimer, 10.0, false, null)
endif
set p = null
return false
endfunction
-
Cinematic Skip Register
-

Events
-


Map initialization
-
-

Conditions
-

Actions
-


Custom script: local trigger t
-


Custom script: local integer i
-


Custom script: set i = 0
-


Custom script: loop
-


Custom script: exitwhen i >= bj_MAX_PLAYER_SLOTS
-


Custom script: if GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(i)) == MAP_CONTROL_USER then
-


Custom script: set t = CreateTrigger()
-


Custom script: call BlzTriggerRegisterPlayerKeyEvent(t, Player(i), OSKEY_ESCAPE, 0, true)
-


Custom script: call TriggerAddCondition(t, Condition(function Cinematic_OnEscPress))
-


Custom script: endif
-


Custom script: set i = i + 1
-


Custom script: endloop
-


Custom script: set t = null
-
-
-
Cinematic Create Dialogs
-

Events
-


Map initialization
-
-

Conditions
-

Actions
-


// Player 1
-


Dialog - Create a dialog titled "Skip Cinematic?"
-


Set Skip_Dialog1 = (Last created dialog)
-


Dialog - Create a button for Skip_Dialog1 labelled "Yes"
-


Set Skip_ButtonYes1 = (Last created dialog button)
-


Dialog - Create a button for Skip_Dialog1 labelled "No"
-


Set Skip_ButtonNo1 = (Last created dialog button)
-


// Player 2
-


Dialog - Create a dialog titled "Skip Cinematic?"
-


Set Skip_Dialog2 = (Last created dialog)
-


Dialog - Create a button for Skip_Dialog2 labelled "Yes"
-


Set Skip_ButtonYes2 = (Last created dialog button)
-


Dialog - Create a button for Skip_Dialog2 labelled "No"
-


Set Skip_ButtonNo2 = (Last created dialog button)
-
-
-
Skip Dialog Click
-

Events
-


Dialog - A dialog button is clicked for Skip_Dialog1
-


Dialog - A dialog button is clicked for Skip_Dialog2
-
-

Conditions
-

Actions
-


// Determine which player and which button
-


If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-



If - Conditions
-




(Clicked dialog) Equal to Skip_Dialog1
-
-



Then - Actions
-




If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-





If - Conditions
-






(Clicked dialog button) Equal to Skip_ButtonYes1
-
-





Then - Actions
-






Set Skip_VoteYes1 = True
-
-





Else
-






Set Skip_VoteYes1 = False // No – cancel vote
-
-
-




Else - Actions
-
-



Else - Actions
-




If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-





If - Conditions
-






(Clicked dialog button) Equal to Skip_ButtonYes2
-
-





Then - Actions
-






Set Skip_VoteYes2 = True
-
-





Else
-






Set Skip_VoteYes2 = False
-
-
-




Else - Actions
-
-
-


// After processing, check if both voted Yes
-


If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-



If - Conditions
-




Skip_VoteYes1 Equal to True
-




Skip_VoteYes2 Equal to True
-
-



Then - Actions
-




// Both want to skip
-




Set Cinematic_Skipped = True
-




Trigger - Run Cinematic Intro End <gen> (ignoring conditions)
-




Trigger - Turn off Cinematic Intro Loop <gen>
-




Set Cinematic_Running = False
-




// Close dialogs
-




Dialog - Hide Skip_Dialog1 for Player 1 (Red)
-




Dialog - Hide Skip_Dialog2 for Player 2 (Blue)
-




// Reset votes
-




Set Skip_VoteYes1 = False
-




Set Skip_VoteYes2 = False
-




Set Skip_VotingActive = False
-




Countdown Timer - Pause Skip_VoteTimer
-
-



Else
-




// If any player voted No, cancel the whole vote
-




If (Skip_VoteYes1 Equal to False) then
-





Set Skip_VoteYes1 = False
-





Set Skip_VoteYes2 = False
-





Dialog - Hide Skip_Dialog1 for Player 1 (Red)
-





Dialog - Hide Skip_Dialog2 for Player 2 (Blue)
-





Set Skip_VotingActive = False
-





Countdown Timer - Pause Skip_VoteTimer
-
-




ElseIf (Skip_VoteYes2 Equal to False) then
-





Set Skip_VoteYes1 = False
-





Set Skip_VoteYes2 = False
-





Dialog - Hide Skip_Dialog1 for Player 1 (Red)
-





Dialog - Hide Skip_Dialog2 for Player 2 (Blue)
-





Set Skip_VotingActive = False
-





Countdown Timer - Pause Skip_VoteTimer
-
-




Else
-





// Both still waiting – do nothing
-
-




EndIf
-
-



EndIf
-
-
-
-
Skip Vote Timeout
-

Events
-


Time - Skip_VoteTimer expires
-
-

Conditions
-

Actions
-


Set Skip_VoteYes1 = False
-


Set Skip_VoteYes2 = False
-


Set Skip_VotingActive = False
-


Dialog - Hide Skip_Dialog1 for Player 1 (Red)
-


Dialog - Hide Skip_Dialog2 for Player 2 (Blue)
-
-
-
Cinematic Intro Start
-

Events
-


Map initialization
-
-

Conditions
-

Actions
-


// ----- Initial setup
-


Sound - Use the Lordaeron Fall daytime ambient theme
-


Sound - Clear the music list
-


Sound - Stop the currently playing music theme
-


Special Effect - Create a special effect at (Target of current camera view) using Daemonic Arrow.mdx
-


Special Effect - Destroy (Last created special effect)
-


Unit - Order Royal Forsaken Captain Mort 0012 <gen> to Hold Position.
-


Unit - Order Warsong Grunt 0016 <gen> to Hold Position.
-


Unit - Order Warsong Grunt 0024 <gen> to Hold Position.
-


Unit - Order Dark Ranger 0006 <gen> to Hold Position.
-


Unit Group - Pick every unit in (Units in Rect 014 <gen>) and do (Actions)
-



Loop - Actions
-




Animation - Play (Picked unit)'s stand ready animation
-
-
-


Animation - Change Kodo Beast 0036 <gen>'s animation speed to 150.00% of its original speed
-


Unit - Hide Cannon Fire dummy 0035 <gen>
-


Unit - Hide Cannon Fire dummy 0034 <gen>
-


// Apply camera for all players
-


Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
-



Loop - Actions
-




Camera - Apply Camera 005 <gen> for (Picked player) over 0.00 seconds
-
-
-


Environment - Set sky to Lordaeron Fall Sky
-


Environment - Set fog to style Linear, z-start 2000.00, z-end 3000.00, density 0.00 and color (30.00%, 30.00%, 5.00%)
-


Cinematic - Turn cinematic mode On for (All players)
-


Cinematic - Fade out over 0.00 seconds using texture Black Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
-


// ----- Start step-based cinematic -----
-


Set Cinematic_Running = True
-


Set Cinematic_Skipped = False
-


Set Cinematic_Phase = 0
-


Set Cinematic_Step = 1
-


Set Cinematic_StepDelay = 1.00
-


Countdown Timer - Start Cinematic_Timer as a One-shot timer that will expire in 1.00 seconds
-


Trigger - Turn on Cinematic Intro Loop <gen>
-
-
-
Cinematic Intro Loop
-

Events
-


Time - Cinematic_Timer expires
-
-

Conditions
-


Cinematic_Running Equal to True
-
-

Actions
-


If (Cinematic_Skipped Equal to True) then
-



Skip remaining actions
-



Trigger - Run Cinematic Intro End <gen> (ignoring conditions)
-



Trigger - Turn off (This trigger)
-



Set Cinematic_Running = False
-
-


Else
-



// ----- Phase 0: Intro cinematic -----
-



If (Cinematic_Phase == 0) then
-




If (Cinematic_Step == 1) then
-





// Step 1: after initial 1 sec – play UC_intro, camera 008, fade in, cannon sound
-





Sound - Play UC_intro <gen>
-





Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
-






Loop - Actions
-







Camera - Apply Camera 008 <gen> for (Picked player) over 0.00 seconds
-
-
-





Cinematic - Fade in over 6.00 seconds using texture White Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
-





Sound - Set Cannon_2 <gen> distances to 1000.00 minimum and 2000.00 maximum
-





Set Cinematic_TempPoint = (Center of Rect 003 <gen>)
-





Sound - Play Cannon_2 <gen> at 30.00% volume, located at Cinematic_TempPoint with Z offset 0.00
-





Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
-





Set Cinematic_StepDelay = 2.00
-
-




ElseIf (Cinematic_Step == 2) then
-





// Wait 2 sec – nothing
-





Set Cinematic_StepDelay = 0.00
-
-




ElseIf (Cinematic_Step == 3) then
-





// Move units from rect 007 to 003 and rect 006 to 008
-





Set Cinematic_TempPoint = (Center of Rect 003 <gen>)
-





Unit Group - Pick every unit in (Units in Rect 007 <gen>) and do (Actions)
-






Loop - Actions
-







Unit - Order (Picked unit) to Move To Cinematic_TempPoint
-
-
-





Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
-





Set Cinematic_TempPoint = (Center of Rect 008 <gen>)
-





Unit Group - Pick every unit in (Units in Rect 006 <gen>) and do (Actions)
-






Loop - Actions
-







Unit - Order (Picked unit) to Move To Cinematic_TempPoint
-
-
-





Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
-





Set Cinematic_StepDelay = 5.00
-
-




ElseIf (Cinematic_Step == 4) then
-





// Wait 5 sec – nothing
-





Set Cinematic_StepDelay = 0.00
-
-




ElseIf (Cinematic_Step == 5) then
-





Set Cinematic_TempPoint = (Center of Rect 008 <gen>)
-





Sound - Play Cannon_4 <gen> at 30.00% volume, located at Cinematic_TempPoint with Z offset 0.00
-





Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
-





Cinematic - Fade out over 2.00 seconds using texture Black Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
-





Set Cinematic_StepDelay = 3.00
-
-




ElseIf (Cinematic_Step == 6) then
-





// Wait 3 sec – nothing
-





Set Cinematic_StepDelay = 0.00
-
-




ElseIf (Cinematic_Step == 7) then
-





// Fog, camera 001, fade in, camera 010, Swish_1
-





Environment - Set fog to style Linear, z-start 3400.00, z-end 4100.00, density 0.00 and color (30.00%, 30.00%, 5.00%)
-





Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
-






Loop - Actions
-







Camera - Apply Camera 001 <gen> for (Picked player) over 0.00 seconds
-
-
-





Cinematic - Fade in over 1.00 seconds using texture White Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
-





Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
-






Loop - Actions
-







Camera - Apply Camera 010 <gen> for (Picked player) over 25.00 seconds
-
-
-





Set Cinematic_TempPoint = (Center of Rect 002 <gen>)
-





Sound - Play Swish_1 <gen> at 100.00% volume, located at Cinematic_TempPoint with Z offset 0.00
-





Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
-





Set Cinematic_StepDelay = 3.50
-
-




ElseIf (Cinematic_Step == 8) then
-





// Wait 3.5 sec – nothing
-





Set Cinematic_StepDelay = 0.00
-
-




ElseIf (Cinematic_Step == 9) then
-





// Explosion at rect 002
-





Set Cinematic_TempPoint = (Center of Rect 002 <gen>)
-





Unit - Create 1 Explosion Dummy for Player 1 (Red) at Cinematic_TempPoint facing Default building facing degrees
-





Unit - Kill (Last created unit)
-





Sound - Play Explosion_2 <gen> at 100.00% volume, located at Cinematic_TempPoint with Z offset 0.00
-





Set Cinematic_TempGroup = (Units within 800.00 of Cinematic_TempPoint)
-





Unit Group - Pick every unit in Cinematic_TempGroup and do (Actions)
-






Loop - Actions
-







Unit - Kill (Picked unit)
-
-
-





Custom script: call DestroyGroup(udg_Cinematic_TempGroup)
-





Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
-





// Play Swish_2 at rect 009
-





Set Cinematic_TempPoint = (Center of Rect 009 <gen>)
-





Sound - Play Swish_2 <gen> at 100.00% volume, located at Cinematic_TempPoint with Z offset 0.00
-





Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
-





Set Cinematic_StepDelay = 2.50
-
-




// ----- Continue all remaining steps from your original trigger -----
-




// Each step corresponds to a wait; uses the original wait durations as step delays.
-




// When you reach the end of the intro, run the end trigger.
-




ElseIf (Cinematic_Step == [last_step]) then
-





Trigger - Run Cinematic Intro End <gen> (ignoring conditions)
-





Trigger - Turn off (This trigger)
-





Set Cinematic_Running = False
-
-




EndIf
-




// After each step, schedule the next
-




Set Cinematic_Step = Cinematic_Step + 1
-




If Cinematic_Step <= [total_steps] then
-





Countdown Timer - Start Cinematic_Timer as a One-shot timer that will expire in Cinematic_StepDelay seconds
-
-




EndIf
-
-



EndIf
-
-


EndIf
-
-
-
Cinematic Intro End
-

Events
-

Conditions
-

Actions
-


// ----- Reset camera and cinematic mode -----
-


Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
-



Loop - Actions
-




Camera - Reset camera for (Picked player) to standard game-view over 0.00 seconds
-
-
-


Cinematic - Fade in over 0.00 seconds using texture White Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
-


Cinematic - Turn cinematic mode Off for (All players)
-


// ----- Final fog and environment (from original end) -----
-


Environment - Set fog to style Linear, z-start 3600.00, z-end 4300.00, density 0.00 and color (30.00%, 30.00%, 5.00%)
-


// ----- Order heroes to move to entrance positions -----
-


Unit - Order Thrall (Unmounted) 0013 <gen> to Attack-Move To (Center of Rect 028 <gen>)
-


Unit - Order Sylvanas 0004 <gen> to Attack-Move To (Center of Rect 028 <gen>)
-


Unit - Order Vol'jin 0037 <gen> to Attack-Move To (Center of Rect 028 <gen>)
-


// ----- Create the 7 ghouls (same as original end) -----
-


Set FallenGhoul_AttackG1 = (Create new unit group)
-


Unit - Create 7 Fallen Ghoul for Player 11 (Dark Green) at (Center of Plague Enterance <gen>) facing Default building facing degrees
-


Wait 0.10 seconds
-


Unit Group - Pick every unit in (Units in Plague Enterance <gen> matching ((Unit-type of (Matching unit)) Equal to Fallen Ghoul)) and do (Actions)
-



Loop - Actions
-




Unit Group - Add (Picked unit) to FallenGhoul_AttackG1
-




Unit - Order (Picked unit) to Attack-Move To (Center of Rect 028 <gen>)
-
-
-


// ----- Ensure entrance trigger is enabled (it should be) -----
-


Trigger - Turn on Enterance <gen>
-


// ----- Enable AI and selection -----
-


Set AI_Enabled = True
-


Selection - Enable Selection: True, Enable Selection Circle: True
-
-
But at the same time… it feels kind of diabolical
So I wanted to ask:
- Is this a reasonable approach for longer / co-op cinematics?
- Are there more standard or simpler ways to handle multiplayer skipping?
- Am I solving a real problem here, or just overcomplicating things?
Any feedback or alternative approaches would be really appreciated.
So this system is supposed to replace inaccurate waits and timing that I’m still struggling to achieve in the original trigger with waits as shown below:

