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

MULTIPLAYER FRIENDLY code needed for obtaining attack damage + bonuses

Level 4
Joined
Apr 6, 2022
Messages
25
Been testing a custom recently and getting random desyncs with my testers, couldn't figure out why until recently I noticed that with certain spells it will show damage done on my screen roughly x10 what my tester sees... the context is this, I have test dummies set up to test spells, when he uses the spell it shows 100 damage, but for me I see 1000 damage... even the test dummies HP shows as being lower on my screen. I see the dummy at 60%, he sees it at like 95%... but when the unit dies, I'm assuming due to this glitch, everyone desyncs. HELP
 
Level 4
Joined
Apr 6, 2022
Messages
25
Yeah it's 100% a desync, not a crash. I have a variety of spells which damage is based on stats like intel or strength, and a few based on WEAPON DAMAGE. I suspect this is the problem. The script used to obtain this is "set udg_XWeaponDamage = ConvertAttackStringToInteger()" and this is put on a variety of triggers so the integer is consistently updated. It seems to work fine when playing solo. Come to think of it, you're the one who helped me with that haha. Thanks again!

I could be wrong, honestly I have no idea why two players would be seeing different damage being done to the same unit. I've never seen this before.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
Yeah it's 100% a desync, not a crash. I have a variety of spells which damage is based on stats like intel or strength, and a few based on WEAPON DAMAGE. I suspect this is the problem. The script used to obtain this is "set udg_XWeaponDamage = ConvertAttackStringToInteger()" and this is put on a variety of triggers so the integer is consistently updated. It seems to work fine when playing solo. Come to think of it, you're the one who helped me with that haha. Thanks again!

I could be wrong, honestly I have no idea why two players would be seeing different damage being done to the same unit. I've never seen this before.
That's funny, yeah, that code I made for you probably isn't multiplayer safe. If you can link me it I can take a look. And remember, the more you show us the better, it's hard to draw any conclusions without seeing actual triggers and code.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
Alright, so the issue was because I needed to Sync the data between each Player. I've attached a new map that uses a more elaborate system since syncing adds some delay before the data is usable. It's also inconsistent since each Player can receive data at different times (as far as I understand).

Now you have "Send" triggers which attempt to get and sync everyone's data (This trigger is syncing Attack values):
  • SAAD Demo Attack Send
    • Events
      • Player - Player 1 (Red) Selects a unit
    • Conditions
    • Actions
      • -------- This checks the "attack info panel" frame for each Player locally and then syncs the data to everyone: --------
      • Custom script: call SyncAttackDataSend()
And you have "Receive" triggers which run automatically by the system when a Player receives their synced data:
  • SAAD Demo Attack Receive
    • Events
    • Conditions
    • Actions
      • -------- This trigger runs whenever a player receives their synced attack data! --------
      • -------- The system will set SAAD_PN to the Player Number of whoever just received their sync data, you can then reference their data like so: --------
      • Game - Display to (All players) for 30.00 seconds the text: Attack:
      • Game - Display to (All players) for 30.00 seconds the text: (String(SAAD_Attack[SAAD_PN]))
Note how the Receive triggers don't have any Events. That's because the SAAD system runs these triggers automatically in response to receiving the associated synced data. You can change what trigger it runs by changing these variables:
  • SAAD Demo Setup
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • -------- These triggers run when a Player receives their synced Attack/Armor data: --------
      • Set VariableSet SAAD_Attack_Trigger = SAAD Demo Attack Receive <gen>
      • Set VariableSet SAAD_Armor_Trigger = SAAD Demo Armor Receive <gen>
Hopefully that makes sense, syncing is a little complicated and I'm new to using it.

In the case of your map, if each player only controls one Hero then it would probably be wise to detect whenever they Select their Hero and call the SyncAttackDataSend() / SyncArmorDataSend() functions in response. Then also periodically call these functions maybe once every 0.50 seconds? Remember, it's going to get you the attack/armor data for whichever unit you had selected at the time of calling the Send() function, even if it's not your main Hero. So you probably want to account for this and make sure that you don't reference data that doesn't belong to your hero, which may prove a little difficult. I suppose you could introduce more variables that store and track the synced Attack/Armor variables, so that you can "compare old and new values", and determine if they seem correct to you. For example, if a boss has 1000 attack damage, and Heroes never go above 100 attack damage, then you'll know that the Player receiving a synced Attack value of around 1000 clearly had a boss selected at the time of syncing and you can disregard that new data and fallback to the last "trusted" value.
 

Attachments

  • SAAD Sync 2.w3m
    21 KB · Views: 7
Last edited:
Level 4
Joined
Apr 6, 2022
Messages
25
Alright, so the issue was because I needed to Sync the data between each Player. I've attached a new map that uses a more elaborate system since syncing adds some delay before the data is usable. It's also inconsistent since each Player can receive data at different times (as far as I understand).

Now you have "Send" triggers which attempt to get and sync everyone's data (This trigger is syncing Attack values):
  • SAAD Demo Attack Send
    • Events
      • Player - Player 1 (Red) Selects a unit
    • Conditions
    • Actions
      • -------- This checks the "attack info panel" frame for each Player locally and then syncs the data to everyone: --------
      • Custom script: call SyncAttackDataSend()
And you have "Receive" triggers which run automatically by the system when a Player receives their synced data:
  • SAAD Demo Attack Receive
    • Events
    • Conditions
    • Actions
      • -------- This trigger runs whenever a player receives their synced attack data! --------
      • -------- The system will set SAAD_PN to the Player Number of whoever just received their sync data, you can then reference their data like so: --------
      • Game - Display to (All players) for 30.00 seconds the text: Attack:
      • Game - Display to (All players) for 30.00 seconds the text: (String(SAAD_Attack[SAAD_PN]))
Note how the Receive triggers don't have any Events. That's because the SAAD system runs these triggers automatically in response to receiving the associated synced data. You can change what trigger it runs by changing these variables:
  • SAAD Demo Setup
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • -------- These triggers run when a Player receives their synced Attack/Armor data: --------
      • Set VariableSet SAAD_Attack_Trigger = SAAD Demo Attack Receive <gen>
      • Set VariableSet SAAD_Armor_Trigger = SAAD Demo Armor Receive <gen>
Hopefully that makes sense, syncing is a little complicated and I'm new to using it.

In the case of your map, if each player only controls one Hero then it would probably be wise to detect whenever they Select their Hero and call the SyncAttackDataSend() / SyncArmorDataSend() functions in response. Then also periodically call these functions maybe once every 0.50 seconds? Remember, it's going to get you the attack/armor data for whichever unit you had selected at the time of calling the Send() function, even if it's not your main Hero. So you probably want to account for this and make sure that you don't reference data that doesn't belong to your hero, which may prove a little difficult. I suppose you could introduce more variables that store and track the synced Attack/Armor variables, so that you can "compare old and new values", and determine if they seem correct to you. For example, if a boss has 1000 attack damage, and Heroes never go above 100 attack damage, then you'll know that the Player receiving a synced Attack value of around 1000 clearly had a boss selected at the time of syncing and you can disregard that new data and fallback to the last "trusted" value.
Hmmmm I don't quite understand the last part. Where is the SAAD Demo Attack Receive variable? Is there a way to do the same with no index?
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
No, the Arrays are essential for it to work. Otherwise, each player would be overwriting each other's data.

Variables:
SAAD_PN = The Player Number of whoever last received their synced data. You would reference this throughout the Receive triggers.
SAAD_Attack[(Player number)] = Always equal to a Player's last received attack data.
SAAD_Armor[(Player number)] = Always equal to a Player's last received armor data.

So if you had 3 players in your map and someone selected their Hero causing SyncAttackDataSend() to get called, all 3 players would run the SAAD Demo Attack Receive trigger in the very near future. Each of them would run this trigger when they receive their synced data. So the Receive triggers act as a way for you to interact with a player's attack/armor data at the exact moment that they get it. And you know exactly who received the data because SAAD_PN tells you their Player Number. Their Player Number also gets you the Player and should also get you their Hero if you're tracking it in a Unit array variable.

Remember, syncing takes time and differs for each player. Each client (user) is sending data to a server which is then relaying that information back to every other client. Everyone has their own slower/faster internet speeds and the distance from the server is a factor as well. The timing delay is what makes this complicated but it's the only way to sync things.

Edit:
If it's easier to understand, you don't actually need to do anything other than call SyncAttackDataSend() and then reference SAAD_Attack[] later on. The Receive trigger stuff is just there to give you the exact moment when this data is received which could come in handy. So feel free to leave the Receive triggers empty or just delete them. But do NOT delete or rename any variables or you'll get errors.
 
Last edited:
Level 4
Joined
Apr 6, 2022
Messages
25
So this happens immediately upon simply copying and pasting your triggers into my map, without any tampering... not sure why.
error2.png
 
Last edited:
Level 4
Joined
Apr 6, 2022
Messages
25
So I deleted all traces of the previous trigger that seemed to be conflicting with it, now getting this error... Doesn't seem like to the code and I cannot understand why because it works fine on the map you sent.
error3.png
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
You need to delete everything related to the old system, that includes all Custom Script tied to it. Then save your map. Then import this system and make sure the code is at the top of the trigger editor.

Also, in your first picture the error is telling you that you cannot call ConvertAttackStringToInteger() like that.
1) You shouldn't be calling it anyway, it desyncs.
2) It now takes a String as a parameter. But don't call it, it desyncs.

Instead, you use the Send() functions to get the attack or armor. See my example Send triggers.
 
Last edited:
Level 4
Joined
Apr 6, 2022
Messages
25
So I first deleted all traces of the first trigger, all custom scripts gone, tested the map and it works fine without. After that I simply copy and paste your triggers into my map and it immediately causes errors. I have no idea what this means but if I tab thru the lines of text it's all your triggers that it doesn't like for some reason.

Is there perhaps an easier way? If the initial trigger was able to obtain the data needed, is there not a way to simply take that data and sync it with other players? I'm getting very exhausted with these random errors.

error5.png

error4.png
 
Level 4
Joined
Apr 6, 2022
Messages
25
I feel bad doing this to you because it's a huge map with like 1200+ triggers thus far, that undoubtedly are going to be messy and confusing to sift through. But nonetheless, here it is lol. In this version of the map I've removed all traces of the previous trigger in question, but your new ones haven't yet been added, I figure it's probably better this way so you can witness the entire process.

What this map is atm is a hero tester, for heroes that will later be used for a moba type game. The idea is you go in, select spells with spell book, test abilities and builds on the test dummies, then enter the arena and try them on various bosses. The spell book gives you one choice per page, the armor and weapon racks give items, the machine on the right activates arena. Thus far everything works smoothly with little to no errors. The only consistent problem has been a handful of spells based on weapon damage, and with the previous trigger removed weapon damaged based abilities wont work in the version I've uploaded. Anyway, apologies in advance for the mess, this is my first map.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
I feel bad doing this to you because it's a huge map with like 1200+ triggers thus far, that undoubtedly are going to be messy and confusing to sift through. But nonetheless, here it is lol. In this version of the map I've removed all traces of the previous trigger in question, but your new ones haven't yet been added, I figure it's probably better this way so you can witness the entire process.

What this map is atm is a hero tester, for heroes that will later be used for a moba type game. The idea is you go in, select spells with spell book, test abilities and builds on the test dummies, then enter the arena and try them on various bosses. The spell book gives you one choice per page, the armor and weapon racks give items, the machine on the right activates arena. Thus far everything works smoothly with little to no errors. The only consistent problem has been a handful of spells based on weapon damage, and with the previous trigger removed weapon damaged based abilities wont work in the version I've uploaded. Anyway, apologies in advance for the mess, this is my first map.
Okay, I downloaded the map. You can delete it from your post now if you don't feel comfortable leaving it up for others to download. I'll edit this comment in a few minutes once I have it figured out.

Edit: I got it working, turns out you somehow had a reference to the old ConvertAttackStringToInteger() function in your Rhazule Damage Recorder trigger.
 

Attachments

  • HELTORWORLD Fix1.w3m
    11.5 MB · Views: 2
Last edited:
Level 4
Joined
Apr 6, 2022
Messages
25
Okay, I downloaded the map. You can delete it from your post now if you don't feel comfortable leaving it up for others to download. I'll edit this comment in a few minutes once I have it figured out.

Edit: I got it working, turns out you somehow had a reference to the old ConvertAttackStringToInteger() function in your Rhazule Damage Recorder trigger.
Thanks so much man. If you accept tips somehow I will gladly give you one.
 
Level 4
Joined
Apr 6, 2022
Messages
25
Is there a way to make the trigger proc from ability usage? I have some spells that increase damage, and using this prior to a spell based on damage doesn't update the variable because the unit isn't reselected. I don't fully understand why player 1 selecting a unit applies to all players.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
That's just a demonstration of how you can use the system, hence why it's a "Demo" trigger. But I understand the confusion.

When you call this function it will begin syncing the Attack value of each player's currently selected unit:
  • Custom script: call SyncAttackDataSend()
Unfortunately, this takes time to sync so you can't just call this and then immediately reference the SAAD_Attack[] variable. Instead, you would call this preemptively and then reference SAAD_Attack[] in your ability trigger.

I've explained syncing in previous posts if you're confused about the concept.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
Working flawlessly single player, but causing crashes with multiple players for some reason
Hmm, that's unfortunate. Maybe someone can help me out here @Tasyen @Bribe @Tasyen

Here's the code, it causes what appears to be a crash. I'm almost certain that it's not an issue with the two Convert() functions at the top.
I assume it's because I'm not syncing data properly (see bottom portion of the code), hopefully someone can point out my mistakes:
vJASS:
library SyncAttackArmorData initializer SyncAttackArmorDataInit

// Reference:
// https://www.hiveworkshop.com/threads/target-of-current-camera-desync.316384/

function ConvertAttackStringToInteger takes string inputStr returns integer
    local string currentChar = ""
    local boolean isSecondHyphen = false
    local integer i = 1
    local integer len = StringLength(inputStr)
    local integer minAttack = 0
    local integer maxAttack = 0
    local integer bonus = 0
    local integer maxIndex = 0

    // Loop through the characters in the input string
    loop
        exitwhen i > len

        set currentChar = SubStringBJ(inputStr, i, i)

        if currentChar == "-" and isSecondHyphen == false then
            set isSecondHyphen = true
            set minAttack = S2I(SubStringBJ(inputStr, 1, i - 1))
            set maxIndex = i + 1
        elseif currentChar == "+" then
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, i - 1))
            set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
            return minAttack + maxAttack + (bonus * 2)
        elseif currentChar == "-" and isSecondHyphen then
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, i - 1))
            set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
            set bonus = bonus * -1
            return minAttack + maxAttack + (bonus * 2)
        endif

        set i = i + 1
        if i >= len then
            // No bonus
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, len))
            return minAttack + maxAttack
        endif
    endloop

    return 0
endfunction

function ConvertArmorStringToReal takes string inputStr returns real
    local string currentChar = ""
    local integer i = 1
    local integer len = StringLength(inputStr)
    local integer armor = 0
    local integer bonus = 0
    local real armorBonus = 0.0

    // Loop through the characters in the input string
    loop
        exitwhen i > len

        set currentChar = SubStringBJ(inputStr, i, i)

        if currentChar == "-" then
            set armor = S2I(SubStringBJ(inputStr, 1, i - 1))
            set armorBonus = S2R(SubStringBJ(inputStr, i + 1, len))
            // Check for int or real bonus armor
            if armorBonus == 0.0 then
                // Bonus is an integer
                set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
                return I2R(armor - bonus)
            else
                // Bonus is a real
                return I2R(armor) - armorBonus
            endif

        elseif currentChar == "+" then
            set armor = S2I(SubStringBJ(inputStr, 1, i - 1))
            set armorBonus = S2R(SubStringBJ(inputStr, i + 1, len))
            // Check for int or real bonus armor
            if armorBonus == 0.0 then
                // Bonus is an integer
                set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
                return I2R(armor + bonus)
            else
                // Bonus is a real
                return I2R(armor) + armorBonus
            endif
        endif

        set i = i + 1
        if i >= len then
            // No bonus
            set armor = S2I(SubStringBJ(inputStr, 1, len))
            return I2R(armor)
        endif
    endloop

    return 0.0
endfunction

function SyncAttackDataRead takes nothing returns nothing
    local string strData = ""
    local integer id = GetConvertedPlayerId(GetTriggerPlayer())
    if BlzGetTriggerSyncPrefix() == "SATK" then
        set strData = BlzGetTriggerSyncData()
        set udg_SAAD_Attack_String[id] = strData
        set udg_SAAD_Attack[id] = ConvertAttackStringToInteger(strData)
        set udg_SAAD_PN = id
        call TriggerExecute(udg_SAAD_Attack_Trigger)
    endif
 endfunction
 
 function SyncArmorDataRead takes nothing returns nothing
    local string strData = ""
    local integer id = GetConvertedPlayerId(GetTriggerPlayer())
    if BlzGetTriggerSyncPrefix() == "SARM" then
        set strData = BlzGetTriggerSyncData()
        set udg_SAAD_Armor_String[id] = strData
        set udg_SAAD_Armor[id] = ConvertArmorStringToReal(strData)
        set udg_SAAD_PN = id
        call TriggerExecute(udg_SAAD_Armor_Trigger)
    endif
 endfunction

 function SyncAttackDataSend takes nothing returns nothing
    call BlzSendSyncData("SATK", BlzFrameGetText(BlzGetFrameByName("InfoPanelIconValue", 0)))
 endfunction
 
 function SyncArmorDataSend takes nothing returns nothing
    call BlzSendSyncData("SARM", BlzFrameGetText(BlzGetFrameByName("InfoPanelIconValue", 2)))
 endfunction
 
 function SyncAttackArmorDataInit takes nothing returns nothing
    local integer playerIndex = 0
    local trigger trig1 = CreateTrigger()
    local trigger trig2 = CreateTrigger()
    call TriggerAddAction(trig1, function SyncAttackDataRead)
    call TriggerAddAction(trig2, function SyncArmorDataRead)
    loop
        call BlzTriggerRegisterPlayerSyncEvent(trig1, Player(playerIndex), "SATK", false)
        call BlzTriggerRegisterPlayerSyncEvent(trig2, Player(playerIndex), "SARM", false)
        set playerIndex = playerIndex + 1
        exitwhen playerIndex == bj_MAX_PLAYER_SLOTS
    endloop
 endfunction

 endlibrary
Sorry to @ everyone but I think I'm making a simple mistake that can be easily spotted. Don't bother if it's too much of a hassle.

Any help is greatly appreciated!
 
Last edited:
Level 4
Joined
Apr 6, 2022
Messages
25
It's distinctly different from the original desync where it would actually show up in the chat log as a desync. This time it's actually crashing the game.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
Sorry, I'm a bit of an amateur. Can you show me how to apply this to a variable?
  • Set Variable MyUnit = (The unit in question)
  • Custom script: set udg_MyRealVariable = GetTotalDamage(udg_MyUnit)
MyRealVariable is a Real variable.

But understand that ThompZon is suggesting that you trigger every single instance of bonus damage in your map using this system. That means an Ability like Command Aura which grants bonus damage and Attack bonuses from an Item like Claws of Attack will NOT work. You would need to trigger these effects yourself.
 
Level 4
Joined
Apr 6, 2022
Messages
25
  • Set Variable MyUnit = (The unit in question)
  • Custom script: set udg_MyRealVariable = GetTotalDamage(udg_MyUnit)
MyRealVariable is a Real variable.

But understand that ThompZon is suggesting that you trigger every single instance of bonus damage in your map using this system. That means an Ability like Command Aura which grants bonus damage and Attack bonuses from an Item like Claws of Attack will NOT work. You would need to trigger these effects yourself.
Ah I see, yeah I'm using claws quite extensively in many cases
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,546
If you're still trying, I adjusted my code in hopes that it won't crash now:
vJASS:
library SyncAttackArmorData initializer SyncAttackArmorDataInit

// Reference:
// https://www.hiveworkshop.com/threads/target-of-current-camera-desync.316384/

function ConvertAttackStringToInteger takes string inputStr returns integer
    local string currentChar = ""
    local boolean isSecondHyphen = false
    local integer i = 1
    local integer len = StringLength(inputStr)
    local integer minAttack = 0
    local integer maxAttack = 0
    local integer bonus = 0
    local integer maxIndex = 0

    // Loop through the characters in the input string
    loop
        exitwhen i > len

        set currentChar = SubStringBJ(inputStr, i, i)

        if currentChar == "-" and isSecondHyphen == false then
            set isSecondHyphen = true
            set minAttack = S2I(SubStringBJ(inputStr, 1, i - 1))
            set maxIndex = i + 1
        elseif currentChar == "+" then
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, i - 1))
            set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
            return minAttack + maxAttack + (bonus * 2)
        elseif currentChar == "-" and isSecondHyphen then
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, i - 1))
            set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
            set bonus = bonus * -1
            return minAttack + maxAttack + (bonus * 2)
        endif

        set i = i + 1
        if i >= len then
            // No bonus
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, len))
            return minAttack + maxAttack
        endif
    endloop

    return 0
endfunction

function ConvertArmorStringToReal takes string inputStr returns real
    local string currentChar = ""
    local integer i = 1
    local integer len = StringLength(inputStr)
    local integer armor = 0
    local integer bonus = 0
    local real armorBonus = 0.0

    // Loop through the characters in the input string
    loop
        exitwhen i > len

        set currentChar = SubStringBJ(inputStr, i, i)

        if currentChar == "-" then
            set armor = S2I(SubStringBJ(inputStr, 1, i - 1))
            set armorBonus = S2R(SubStringBJ(inputStr, i + 1, len))
            // Check for int or real bonus armor
            if armorBonus == 0.0 then
                // Bonus is an integer
                set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
                return I2R(armor - bonus)
            else
                // Bonus is a real
                return I2R(armor) - armorBonus
            endif

        elseif currentChar == "+" then
            set armor = S2I(SubStringBJ(inputStr, 1, i - 1))
            set armorBonus = S2R(SubStringBJ(inputStr, i + 1, len))
            // Check for int or real bonus armor
            if armorBonus == 0.0 then
                // Bonus is an integer
                set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
                return I2R(armor + bonus)
            else
                // Bonus is a real
                return I2R(armor) + armorBonus
            endif
        endif

        set i = i + 1
        if i >= len then
            // No bonus
            set armor = S2I(SubStringBJ(inputStr, 1, len))
            return I2R(armor)
        endif
    endloop

    return 0.0
endfunction

function SyncAttackDataRead takes nothing returns nothing
    if BlzGetTriggerSyncPrefix() == "SATK" then
        set udg_SAAD_Attack[GetConvertedPlayerId(GetTriggerPlayer())] = ConvertAttackStringToInteger(BlzGetTriggerSyncData())
    endif
 endfunction
 
 function SyncArmorDataRead takes nothing returns nothing
    if BlzGetTriggerSyncPrefix() == "SARM" then
        set udg_SAAD_Armor[GetConvertedPlayerId(GetTriggerPlayer())] = ConvertArmorStringToReal(BlzGetTriggerSyncData())
    endif
 endfunction

 function SyncAttackDataSend takes nothing returns nothing
    call BlzSendSyncData("SATK", BlzFrameGetText(BlzGetFrameByName("InfoPanelIconValue", 0)))
 endfunction
 
 function SyncArmorDataSend takes nothing returns nothing
    call BlzSendSyncData("SARM", BlzFrameGetText(BlzGetFrameByName("InfoPanelIconValue", 2)))
 endfunction
 
 function SyncAttackArmorDataInit takes nothing returns nothing
    local integer playerIndex = 0
    local trigger trig1 = CreateTrigger()
    local trigger trig2 = CreateTrigger()
    call TriggerAddAction(trig1, function SyncAttackDataRead)
    call TriggerAddAction(trig2, function SyncArmorDataRead)
    loop
        call BlzTriggerRegisterPlayerSyncEvent(trig1, Player(playerIndex), "SATK", false)
        call BlzTriggerRegisterPlayerSyncEvent(trig2, Player(playerIndex), "SARM", false)
        set playerIndex = playerIndex + 1
        exitwhen playerIndex == bj_MAX_PLAYER_SLOTS
    endloop
 endfunction

 endlibrary
I simplified the Sync stuff, hopefully getting rid of the trouble maker, now all it does is Set the SAAD_Attack[] or SAAD_Armor[] variables.

And here's an even more simplified version, you probably just want to use this. It only Sets the SAAD_Attack_String[] and SAAD_Armor_String[] variables. You'll need to manually call ConvertAttackStringToInteger() and ConvertArmorStringToReal() using these as the parameter.
vJASS:
library SyncAttackArmorData initializer SyncAttackArmorDataInit

// Reference:
// https://www.hiveworkshop.com/threads/target-of-current-camera-desync.316384/

function ConvertAttackStringToInteger takes string inputStr returns integer
    local string currentChar = ""
    local boolean isSecondHyphen = false
    local integer i = 1
    local integer len = StringLength(inputStr)
    local integer minAttack = 0
    local integer maxAttack = 0
    local integer bonus = 0
    local integer maxIndex = 0

    // Loop through the characters in the input string
    loop
        exitwhen i > len

        set currentChar = SubStringBJ(inputStr, i, i)

        if currentChar == "-" and isSecondHyphen == false then
            set isSecondHyphen = true
            set minAttack = S2I(SubStringBJ(inputStr, 1, i - 1))
            set maxIndex = i + 1
        elseif currentChar == "+" then
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, i - 1))
            set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
            return minAttack + maxAttack + (bonus * 2)
        elseif currentChar == "-" and isSecondHyphen then
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, i - 1))
            set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
            set bonus = bonus * -1
            return minAttack + maxAttack + (bonus * 2)
        endif

        set i = i + 1
        if i >= len then
            // No bonus
            set maxAttack = S2I(SubStringBJ(inputStr, maxIndex, len))
            return minAttack + maxAttack
        endif
    endloop

    return 0
endfunction

function ConvertArmorStringToReal takes string inputStr returns real
    local string currentChar = ""
    local integer i = 1
    local integer len = StringLength(inputStr)
    local integer armor = 0
    local integer bonus = 0
    local real armorBonus = 0.0

    // Loop through the characters in the input string
    loop
        exitwhen i > len

        set currentChar = SubStringBJ(inputStr, i, i)

        if currentChar == "-" then
            set armor = S2I(SubStringBJ(inputStr, 1, i - 1))
            set armorBonus = S2R(SubStringBJ(inputStr, i + 1, len))
            // Check for int or real bonus armor
            if armorBonus == 0.0 then
                // Bonus is an integer
                set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
                return I2R(armor - bonus)
            else
                // Bonus is a real
                return I2R(armor) - armorBonus
            endif

        elseif currentChar == "+" then
            set armor = S2I(SubStringBJ(inputStr, 1, i - 1))
            set armorBonus = S2R(SubStringBJ(inputStr, i + 1, len))
            // Check for int or real bonus armor
            if armorBonus == 0.0 then
                // Bonus is an integer
                set bonus = S2I(SubStringBJ(inputStr, i + 1, len))
                return I2R(armor + bonus)
            else
                // Bonus is a real
                return I2R(armor) + armorBonus
            endif
        endif

        set i = i + 1
        if i >= len then
            // No bonus
            set armor = S2I(SubStringBJ(inputStr, 1, len))
            return I2R(armor)
        endif
    endloop

    return 0.0
endfunction

function SyncAttackDataRead takes nothing returns nothing
    if BlzGetTriggerSyncPrefix() == "SATK" then
        set udg_SAAD_Attack_String[GetConvertedPlayerId(GetTriggerPlayer())] = BlzGetTriggerSyncData()
    endif
 endfunction
 
 function SyncArmorDataRead takes nothing returns nothing
    if BlzGetTriggerSyncPrefix() == "SARM" then
        set udg_SAAD_Armor_String[GetConvertedPlayerId(GetTriggerPlayer())] = BlzGetTriggerSyncData()
    endif
 endfunction

 function SyncAttackDataSend takes nothing returns nothing
    call BlzSendSyncData("SATK", BlzFrameGetText(BlzGetFrameByName("InfoPanelIconValue", 0)))
 endfunction
 
 function SyncArmorDataSend takes nothing returns nothing
    call BlzSendSyncData("SARM", BlzFrameGetText(BlzGetFrameByName("InfoPanelIconValue", 2)))
 endfunction
 
 function SyncAttackArmorDataInit takes nothing returns nothing
    local integer playerIndex = 0
    local trigger trig1 = CreateTrigger()
    local trigger trig2 = CreateTrigger()
    call TriggerAddAction(trig1, function SyncAttackDataRead)
    call TriggerAddAction(trig2, function SyncArmorDataRead)
    loop
        call BlzTriggerRegisterPlayerSyncEvent(trig1, Player(playerIndex), "SATK", false)
        call BlzTriggerRegisterPlayerSyncEvent(trig2, Player(playerIndex), "SARM", false)
        set playerIndex = playerIndex + 1
        exitwhen playerIndex == bj_MAX_PLAYER_SLOTS
    endloop
 endfunction

 endlibrary

An example:
  • Actions
    • Custom script: call SyncAttackDataSend()
    • Custom script: call SyncArmorDataSend()
    • Wait 0.50 game-time seconds
    • Set Variable PN = (Player number of whoever you want)
    • Custom script: set udg_SAAD_Attack[udg_PN] = ConvertAttackStringToInteger( udg_SAAD_Attack_String[udg_PN] )
    • Custom script: set udg_SAAD_Armor[udg_PN] = ConvertArmorStringToReal( udg_SAAD_Armor_String[udg_PN] )
SAAD_Attack[PN] and SAAD_Armor[PN] will be usable like normal variables after the last bit of custom script.
 
Last edited:
Top