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

Map desync reduction & optimization

Status
Not open for further replies.
Level 5
Joined
Jan 23, 2020
Messages
86
I've asked for some optimization suggestions before, which helped reduce the frequency of desyncs players are experiencing.

But there are still players dropping from games, at least every 2/5 games.

Can anyone comb over my map and let me know if there are any issues or optimizations I could do to reduce desyncs.

Thanks!
 

Attachments

  • City TD Solo v2.80a.w3x
    2.3 MB · Views: 16

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
Alright, so I went through and cleaned up just about very leak I could find.

And I think I found your desync culprit. GetLocalPlayer() will cause desyncs if not used correctly:
  • Actions
    • Custom script: if(GetLocalPlayer() == Player(0)) then
    • Sound - Play QuestNew <gen>
    • Custom script: endif
I believe the proper way to play a sound for a single player is with a Sound variable like this:
  • Actions
    • Set VariableSet TempSound = No sound
    • Custom script: if GetLocalPlayer() == GetTriggerPlayer() then
    • Set VariableSet TempSound = QuestNew <gen>
    • Custom script: endif
    • Sound - Play TempSound
This will play No Sound for other players, and QuestNew for our Local Player.

If the desyncs keep happening even with this fix you can try deleting GetLocalPlayer() entirely instead to see if that fixes it once and for all.

I also went through and revamped some triggers and tried to fix any errors I could find. I noticed you were doing a lot of "Destroy last created something" for Items, Sounds, Special Effects, even in cases where nothing existed.

This is a problem and you need to make sure you don't do this, as you will destroy the incorrect thing or attempt to destroy something that doesn't exist. Same goes for if you try to do this after a Wait. Remember, during that Wait another trigger can go off that creates a new thing that is now treated as the "last created" of it's kind. Same goes for Point variables when trying to clean up leaks.

Make sure you're only ever referencing something immediately after creating it (or at least before you create something else of it's kind) when using the "Last created" function.

Oh and the Item Drops were extremely confusing to me. In a lot of your triggers you were doing something like this:
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • ItemDrop Greater than 1990
    • Then - Actions
      • Item - Create Ironwood Branch at (Position of (Triggering unit))
      • Item - Remove (Last created item)
    • Else - Actions
      • Set VariableSet ItemDrop = (Random integer number between 1 and 2000)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • ItemDrop Greater than 1998
        • Then - Actions
          • Item - Create Spitting Penguin at (Position of (Triggering unit))
          • Item - Remove (Last created item)
        • Else - Actions
Why are you creating and then immediately removing the created Item? I don't understand. This results in nothing happening as far as I can tell.

I also went through your Boss Actions folder and replaced anything with Wait Until Condition:
  • Wait until ((Life of (Triggering unit)) Less than 34000.00), checking every 2.00 seconds
With a Unit Group and Loop trigger that checks the units Life periodically.

A lot of triggers confused me:
  • Music end
    • Events
      • Unit - A unit enters (Playable map area)
    • Conditions
      • (Owner of (Triggering unit)) Equal to Player 12 (Brown)
      • Level_Number[(Player number of (Picked player))] Greater than or equal to 51
    • Actions
      • Player Group - Pick every player in (All players) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Troll Beserker lvl 51 Equal to (Unit-type of (Triggering unit))
              • War Commander lvl 52 Equal to (Unit-type of (Triggering unit))
              • Far Seer lvl 53 Equal to (Unit-type of (Triggering unit))
              • Pit Lord lvl 54 Equal to (Unit-type of (Triggering unit))
              • Leader of the Marauders lvl 57 Equal to (Unit-type of (Triggering unit))
            • Then - Actions
              • Camera - Shake the camera for (Picked player) with magnitude 3.00
              • Sound - Play Doom.
              • Sound - Set music volume to 70.00%
              • Sound - Destroy (Last played sound)
              • Wait 10.00 seconds
              • Camera - Stop swaying/shaking the camera for (Picked player).
            • Else - Actions
What is going on here? Level_Number[player number of picked player] is checked in the conditions but there is no Picked player in response to this Event. Not to mention a list of other problems. What is the purpose of this trigger?

Another thing, you try to change the scale of special effect attachments:
  • Special Effect - Create a special effect attached to the overhead of (Triggering unit) using Abilities\Spells\Orc\Shockwave\ShockwaveMissile.mdl
  • Special Effect - Set Scale of (Last created special effect) to 0.80
This only works for Non-Attachment Special Effects.

///
Anyway, hopefully I didn't break anything in the process of cleaning up the map. I went through EVERY-SINGLE-TRIGGER so I wouldn't be surprised if I made a small error somewhere... That being said, I didn't change anything drastically so you shouldn't have a hard time fixing any minor mistakes I made.
 

Attachments

  • City TD Solo v2.80a U1.w3x
    2.3 MB · Views: 24
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
875
And I think I found your desync culprit. GetLocalPlayer() will cause desyncs if not used correctly:
  • actions.gif
    Actions
    • join.gif
      page.gif
      Custom script: if(GetLocalPlayer() == Player(0)) then
    • join.gif
      sound.gif
      Sound - Play QuestNew <gen>
    • joinbottom.gif
      page.gif
      Custom script: endif


I believe the proper way to play a sound for a single player is with a Sound variable like this:
  • actions.gif
    Actions
    • join.gif
      set.gif
      Set VariableSet TempSound = No sound
    • join.gif
      page.gif
      Custom script: if GetLocalPlayer() == GetTriggerPlayer() then
    • join.gif
      set.gif
      Set VariableSet TempSound = QuestNew <gen>
    • join.gif
      page.gif
      Custom script: endif
    • joinbottom.gif
      sound.gif
      Sound - Play TempSound

This will play No Sound for other players, and QuestNew for our Local Player.

This is true because of the PlaySoundBJ wrapper blizzard made for this :
JASS:
function PlaySoundBJ takes sound soundHandle returns nothing
    set bj_lastPlayedSound = soundHandle
    if (soundHandle != null) then
        call StartSound(soundHandle)
    endif
endfunction

So the easiest way would rather be like this :

  • Actions
    • Custom script: if(GetLocalPlayer() == Player(0)) then
    • Custom script: call StartSound(gg_snd_QuestNew)
    • Custom script: endif
:)
 
Level 5
Joined
Jan 23, 2020
Messages
86
@Uncle Thank you so so much! I've gone through your changes and this is a lot more streamlined. There's a lot I did not pick up, especially with my lack of understanding of conditions.

I do have one question regarding hero deaths. I have a trigger that goes off when a heroes dies, "hero will respawn in X" seconds. But with the blademaster hero when he activates mirror image, that message will pop up. I'm guessing because the hero temporarily dies to create illusions of himself? How can I prevent this? Also, would it be "stacking up" the revive event causing desyncs? I've tried adding a 1-second wait to his death condition action but it will persist.

Testing this new build soon and will report back anything else I find!
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
I'm no expert on desyncs and this might be a serious simplification of it, but as far as I know desyncs should only happen when data isn't synced between players. For example, if you were to use GetLocalPlayer() and create a Footman for a player but nobody else. The game will immediately desync and crash because something is happening for one player that isn't happening for the other players. You can imagine what it would be like to get attacked by a Footman that doesn't exist for you :p

The Blademaster Illusion thing is rather weird. Your revive triggers use the Event "A unit dies" correct? I've never experienced that issue before and it sounds like a Reforged bug that should definitely not be happening. The Hero does not die, it's made Invulnerable and set to Hidden (like what happens when you enter a Goblin Zeppelin), and then after the Images are created it's set back to Vulnerable/Unhidden. At least that's my assumption on it's mechanics. Regardless that should definitely not be firing the Event.
 
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
875
Desyncs happen when you create any handle in the local player context.

Different values for a same variable will only desync if it is used to alter interactive agents in the synced context (it does not include passive agents like UI, visuals, sounds and music). In fact, I believe that desync exclusively happen when anything alters the interactivity in any way.

This is the reason why there are sometimes problems with terrain deformations, because the terrain Z value can be altered differently on different systems, and this Z value does affect several interactive agents, like widgets.
Although maybe Blizzard would have recently fixed that (I hope).

Overall, anything that just affects visibility should not desync.
You can even render a unit invisible for some users and not for others without even risking a desync.

There was absolutely nothing wrong with your solution, the one I posted was just some kind of simplification.
 
Level 9
Joined
Mar 26, 2017
Messages
376
This is true because of the PlaySoundBJ wrapper blizzard made for this :
JASS:
function PlaySoundBJ takes sound soundHandle returns nothing
    set bj_lastPlayedSound = soundHandle
    if (soundHandle != null) then
        call StartSound(soundHandle)
    endif
endfunction

So the easiest way would rather be like this :

  • Actions
    • Custom script: if(GetLocalPlayer() == Player(0)) then
    • Custom script: call StartSound(gg_snd_QuestNew)
    • Custom script: endif
:)

I'm just curious, what part in that BJ wrapper might cause a desync? All it does is set a sound variable and use an if condition on calling StartSound (which by itself is no problem). It looks to me like it doesn't create a handle or modify any synced property.
 
Level 12
Joined
Jan 30, 2020
Messages
875
Yes you are indeed 100% right.

I think I should pay more attention to the code I borrow from Blizzard.j :)
There used to be a function to play a sound in Blizzard.j that was renowned to disconnect, but I can't seem to remember what it was.

EDIT :
Found it, and it's definitely not the one used by GUI.
Fun enough I haven't been able to find anything in the game that uses it :O
But maybe it's because I haven't used GUI since 2003, so I am quite outdated on the matter :/

So here was that function that did indeed desync for obvious reasons, unlike PlaySoundBJ :
JASS:
function PlaySound takes string soundName returns nothing
    local sound soundHandle = CreateSound(soundName, false, false, true, 12700, 12700, "")
    call StartSound(soundHandle)
    call KillSoundWhenDone(soundHandle)
endfunction

If anyone knows if this is used anywhere in the game, I would love to know.
 
Level 12
Joined
Jan 30, 2020
Messages
875
It's mostly a wrapper of the StartSound native in a local player context :
JASS:
function StartSoundForPlayerBJ takes player whichPlayer, sound soundHandle returns nothing
    if (whichPlayer == GetLocalPlayer()) then
        call StartSound(soundHandle)
    endif
endfunction
And yes that does not desync at all.

But I would advise to use 3 custom script lines rather than bloating your code with useless BJ wrappers. considering how many of these GUI already brings to the table.
  • Custom Script : if (Player(0)== GetLocalPlayer()) then
  • Custom Script : call StartSound(QuestNew <gen>)
  • Custom Script :endif
 
Status
Not open for further replies.
Top