1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still haven't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. Ride into the sunset with the 32nd Modeling Contest. The contest is optionally paired. Best of luck, people!
    Dismiss Notice
  4. This adventure has come to an end. Congratulate our heroes in the 16th Mini Mapping Contest Results.
    Dismiss Notice
  5. From the gates of hell, the 5th Special Effect Contest Results have emerged.
    Dismiss Notice
  6. Race against the odds and Reforge, Don't Refund. The 14th Techtree Contest has begun!
    Dismiss Notice
  7. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Save/Load Compression

Discussion in 'Trigger (GUI) Editor Tutorials' started by Hoernchen, Aug 28, 2008.

  1. Hoernchen

    Hoernchen

    Joined:
    Jun 16, 2007
    Messages:
    365
    Resources:
    2
    Maps:
    1
    Tutorials:
    1
    Resources:
    2
    This tutorial will NOT teach you how to make save/load codes, I assume that you know how to create basic save/load codes.
    This will teach you how to reduce the size of your save/load codes when used and how to store as much information in them as possible.

    Index:
    1. Code-Library
    2. Logical Exclusion
    3. String Checking
    4. Boolean Compression
    5. Indexing Values
    6. Attached Map

    1. Code-Library

    Create a "Library" in which you assign the CodeLetter variable to all your letters used in your save/load. It has to start at 0 and go on to a higher number, preferably 64. For each additional letter you get, your code becomes a little shorter.

    • Library
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set CodeLetter[0] = f
      • Set CodeLetter[1] = 4
      • -------- Keep assigning values, the more the better--------
      • Set CodeLetter[64] = %
      • -------- I STRONGLY recommend assigning 64 letters, as this ensures maximum data storage!--------
    Don't forget that you can use numbers(0-9), signs(!, §, $, %, &...=,?,*), uppercase(A-Z) and lowercase letters(a-z) not just a-z!

    2. Logical Exclusion

    A smart thing to do when saving stuff to your codes is to use logic in order to cancel out as many variables as possible! If you save the number of games played, won and lost by a person you can drop one of these values for example, as they all can be found using the other two, Games Played = Wins + Losses. Try to use values that can find other values all the time!

    Another great way is to use mutually exclusive events! Lets say you want to save a Hero's Weapon, but all Heroes have different weapons that they can wear...You simply assign the items that are mutually exclusive (like a druid cant carry a sword, knight cant carry staff) to the same variable!
    Then check the unit-type to see what item it would logically wear when loading the code.

    3. String Checking

    You should never ever save a complete string! It takes too much space. Always look for characteristics of the string in order to create a code for it. Hoernchen for example could be converted using:

    Code (vJASS):

    function name2int takes string s returns integer i
      local integer i = 0
      local integer result = 0
      loop
        exitwhen i == StringLength(s)
        if SubString(s, i, i+1) == a vowel then
            set result = result + i + 1
        endif
        set i = i + 1
      endloop
      return result * StringLength(s)
    endfunction
     
    This function would loop through a name and each time it hits a vowel it would add the position of the vowel in the name to the result.
    So hOErnchEn = 2+3+8 = 13 * 9 = 117
    This can be done several different ways to achieve fairly unique integers for a string.

    4. Boolean Compression

    Most ORPGs have quests. Now, I haven't seen a single one to save the quests you have completed...this is how to while using near to no letters!

    A completed quest or any boolean variable is basically just containing the value 1 or 0. This is called binary.
    Binary is like you imagining you had 1 finger per hand and you counted with your fingers instead with 10.
    E.g.:
    0 = 0
    1 = 1
    2 = 10
    3 = 11
    4 = 100
    9 = 1001
    ....

    Now, 63 = 111111. This matters cause we are using CodeLetters up to 64. As you can see, the number 63 is in this case 6 letters. Imagine each of these letters was a boolean! That would mean that they all are true, because they all equal 1! But 62 = 111110 meaning that all except boolean 6 are true.
    This means you can save your booleans and quests into binary format in order to save 6 BOOLEANS INTO ONE LETTER!

    This will compress 6 boolean values into a single letter:
    Code (vJASS):

    function Compress takes nothing returns string
      local boolean array bool
      local integer result = 0
      local integer i = 6
      set bool[5] = true  //2^0 * 1 = 1
      set bool[4] = false //2^1 * 0 = 0
      set bool[3] = false //2^2 * 0 = 0
      set bool[2] = true //2^3 * 1 = 8
      set bool[1] = true //2^4 * 1 = 16
      set bool[0] = false //2^5 * 0 = 0
      //which then adds up to 1+8+16 = 25 = 011001
      loop
        exitwhen i == 0
        set i = i - 1
        if bool[i] == true then
          set result = result + Pow(2, 5-i)
        endif
      endloop
      return udg_CodeLetter[result]
    endfunction
     
    This will turn an integer into 6 boolean values:
    Code (vJASS):

    function decodebinary takes integer x returns nothing
      local boolean array bool
      local integer i = 6
      loop
        exitwhen i == 0
        set i = i - 1
        if x / Pow(2,i) == 1 then //e.g.: x / 2^4 == 1
          set x = x - Pow(2,i) //e.g.: 25 - 2^4 = 9
          set bool[i] = true
        else
          set bool[i] = false
        endif
      endloop
    endfunction
     
    So, using these two triggers you can convert a number from 0 to 63 into 6 booleans and vice versa.

    5. Indexing Values

    This is a small trick to decrease length of codes by up to 50%! It is based on the simple idea, that why use space for numbers that aren't there?
    Let's say you want to save an integer from 0-250. To convert this to code itd have to be at least 2 letters, cause 63 < 250 and 4095 > 250.
    But what if the value equals 0? Then your saved code would be 00. And if you have a number under 64 then itd be 0x.
    Notice how the 0 keeps popping up?

    The solution is an index value! (I came up with that). Look at this code:
    xx-xx-xx (Saves 3 values à two letters), if u had 0 for all 3 you would have wasted 6 spaces. So you simply create an additional letter to let you know if there is empty sets of information, so you can leave them out!
    We will work in a 0-2 system, where 0 means the number in a set equals 0, 1 means it is less than 64 and 2 means it uses both letters.
    So with 3 sets that means: x1*9 + x2*3 + x3*1, where the x represent the number of letters used by a set.
    So: xx-x-0 would be: 2*9 + 1*3 + 0 * 1 = 21
    This way we know if the index number is 21, the first set uses 2, second 1 and last set no letters. And by knowing this we do not have to display them anymore!
    so xx000x turns to xxx + CodeLetter[19], saving us 2 entire spots!
    Furthermore one index can be used to cover up to 6 other index values!

    This means that someone who saves the game without doing a lot and without owning a lot can have a save/load code of a laughable length of 4-6! However as players progress through the game and gain higher values the length of their save/load will proportionally increase, which is not a problem, because by now they are so into your map they won't care about long codes anyway!

    6. Attached Map

    I have attached a map I have made using the Index method and the Logical Exclusion in order to produce short save/loads to this post. I regret that it is slightly older than my current knowledge and therefore contains 50 instead of 64 values and does not use boolean compression.
    Note: This map was NOT made for the tutorial in specific

    Script of the map:

    Code (vJASS):

    ////
    //Coding System for Save [Script Header]
    ////

    function encode50 takes integer i, boolean short returns string
        local integer first
        local integer second
        local string str
        set first = (i / 50)
        set second = (i - (first*50))
        if ((first + second) != 0) then
            set str = udg_CodeLibrary[second]
        else
            set str = ""
        endif
        if (first != 0) then
            set str = (udg_CodeLibrary[first] + str)
        endif
        if (short == false) then
            set str = (udg_CodeLibrary[first] + udg_CodeLibrary[second])
        endif
        set first = 0
        set second = 0
        return str
    endfunction

    function CodeExist takes string str returns boolean
        local integer i
        if (StringLength(str) != 1) then
            return false
        endif
        if (str == "") then
            return false
        endif
        set i = 0
        loop
            exitwhen i == 51
            if (udg_CodeLibrary[i] == str) then
                return true
            endif
            set i = (i + 1)
        endloop
        return false
    endfunction

    function decode50 takes string str returns integer
        local integer first_i
        local integer second_i
        local string first
        local string second
        if (StringLength(str) < 1) then
            return 0
        endif
        if (StringLength(str) > 1) then
            set first = SubString(str, 0, 1)
            set second = SubString(str, 1, 2)
        else
            set first = udg_CodeLibrary[0]
            set second = SubString(str, 0, 1)
        endif
        if (CodeExist(first) == false) then
            return 0
        endif
        if (CodeExist(second) == false) then
            return 0
        endif
        set first_i = 0
        loop
            exitwhen udg_CodeLibrary[first_i] == first
            set first_i = (first_i + 1)
        endloop
        set first_i = (first_i * 50)
        set second_i = 0
        loop
            exitwhen udg_CodeLibrary[second_i] == second
            set second_i = (second_i + 1)
        endloop
        set first = null
        set second = null
        return (first_i + second_i)
    endfunction

    function nameValue takes string str returns integer
        local integer i
        local integer result
        set i = 0
        set result = StringLength(str)
        loop
            exitwhen i >= StringLength(str)
            if (SubString(str, i, (i + 1)) == "a" or SubString(str, i, (i + 1)) == "e" or SubString(str, i, (i + 1)) == "i" or SubString(str, i, (i + 1)) == "o" or SubString(str, i, (i + 1)) == "u") then
                set result = (result * 2)
            endif
            set i = i + 1
        endloop
        set i = 0
        if (result > 1000) then
            set result = (StringLength(str) * 3)
        endif
        return result
    endfunction

    function createCS takes integer i returns integer
        local integer CS
        set CS = 8
        set CS = (CS + udg_SAVE_Player_Wins[i])
        set CS = (CS + udg_SAVE_Player_Losses[i])
        set CS = (CS + udg_SAVE_Player_OFF[i])
        set CS = (CS + udg_SAVE_Player_DEF[i])
        set CS = (CS + nameValue(udg_PlayerNameOriginal[i]))
        return CS
    endfunction

    function createIndex takes integer i returns integer
        local integer indexI
        set indexI = 1
        set indexI = indexI + (StringLength(encode50(udg_SAVE_Player_Losses[i], true)) * 9)
        set indexI = indexI + (StringLength(encode50(udg_SAVE_Player_OFF[i], true)) * 3)
        set indexI = indexI + StringLength(encode50(udg_SAVE_Player_DEF[i], true))
        return indexI  
    endfunction

    function getRank takes integer xp returns integer
        if (xp >= 1000) then
            return 18
        endif
        if (xp >= 750) then
            return 17
        endif
        if (xp >= 500) then
            return 16
        endif
        if (xp >= 325) then
            return 15
        endif
        if (xp >= 250) then
            return 14
        endif
        if (xp >= 200) then
            return 13
        endif
        if (xp >= 150) then
            return 12
        endif
        if (xp >= 100) then
            return 11
        endif
        if (xp >= 85) then
            return 10
        endif
        if (xp >= 70) then
            return 9
        endif
        if (xp >= 55) then
            return 8
        endif
        if (xp >= 45) then
            return 7
        endif
        if (xp >= 35) then
            return 6
        endif
        if (xp >= 25) then
            return 5
        endif
        if (xp >= 20) then
            return 4
        endif
        if (xp >= 15) then
            return 3
        endif
        if (xp >= 7) then
            return 2
        endif
        if (xp >= 3) then
            return 1
        endif
        return 0
    endfunction

    function create50 takes integer i returns nothing
        local string array CodePart
        set CodePart[0] = "|c00ffcc00" + encode50(createCS(i), false) + "|r"
        set CodePart[1] = "|c006969ff" + encode50(udg_SAVE_Player_Wins[i], false) + "|r"
        set CodePart[2] = "|c00ff0000" + encode50(udg_SAVE_Player_Losses[i], true) + "|r"
        set CodePart[3] = "|c0000ff00" + encode50(udg_SAVE_Player_OFF[i], true) + "|r"
        set CodePart[4] = "|c00ff6969" + encode50(udg_SAVE_Player_DEF[i], true) + "|r"
        set CodePart[5] = "|c00999999" + encode50(createIndex(i), true) + "|r"
        call DisplayTimedTextToPlayer(Player(i - 1), 0, 0, 600.00, ("|c00ffcc00Your Code Is: |r" + CodePart[0] + CodePart[1] + CodePart[2] + CodePart[3] + CodePart[4] + CodePart[5]))
    endfunction
     
    Code (vJASS):

    ///////
    //Load System
    ///////
    function checkIndex takes integer c, integer spot returns integer
        //Returns the String Length of an Integer
        local integer array i
        set i[3] = (c / 9)
        set i[2] = ((c - (i[3] * 9)) / 3)
        set i[1] = (c - ((i[3] * 9) + (i[2] * 3)))
        return i[spot]
    endfunction

    function read50 takes nothing returns nothing
        local item bagitem
        local integer i = GetConvertedPlayerId(GetTriggerPlayer())
        local string str = SubString(GetEventPlayerChatString(), 6, StringLength(GetEventPlayerChatString()))
        local integer array CodePart
        set CodePart[0] = (decode50(SubString( str, 0, 2 )) - 8)
        set CodePart[1] = decode50(SubString( str, 2, 4 ))
        set CodePart[5] = (decode50(SubString( str, (StringLength(str) - 1), StringLength(str) )) - 1)
        set CodePart[2] = decode50(SubString( str, 4, (4 + checkIndex(CodePart[5], 3)) ))
        set CodePart[3] = decode50(SubString( str, (4 + checkIndex(CodePart[5], 3)), (4 + checkIndex(CodePart[5], 3) + checkIndex(CodePart[5], 2)) ))
        set CodePart[4] = decode50(SubString( str, (4 + checkIndex(CodePart[5], 3) + checkIndex(CodePart[5], 2)), (4 + checkIndex(CodePart[5], 3) + checkIndex(CodePart[5], 2) + checkIndex(CodePart[5], 1)) ))
        if (CodePart[0] != CodePart[1] + CodePart[2] + CodePart[3] + CodePart[4] + nameValue(udg_PlayerNameOriginal[i])) then
            call DisplayTextToPlayer(Player(i - 1), 0, 0, "|c00ff0000Invalid Load Code Entered|r")
            return
        endif
        set udg_SAVE_Player_Wins[i] = CodePart[1]
        set udg_SAVE_Player_Losses[i] = CodePart[2]
        set udg_SAVE_Player_OFF[i] = CodePart[3]
        set udg_SAVE_Player_DEF[i] = CodePart[4]
        set udg_SAVENOT_Player_Games[i] = (CodePart[1] + CodePart[2])
        if (udg_SAVENOT_Player_Games[i] < 1) then
            set udg_SAVE_Player_Wins[i] = 0
            set udg_SAVE_Player_Losses[i] = 0
            set udg_SAVENOT_Player_Games[i] = 0
            set udg_SAVE_Player_OFF[i] = 0
            set udg_SAVE_Player_DEF[i] = 0
            call DisplayTextToPlayer(Player(i - 1), 0, 0, "|c00ff0000Invalid Load Code Entered|r")
            return
        endif
        set udg_SAVENOT_Player_XP[i] = ((CodePart[1] * 5) + (CodePart[2] * 3))
        set udg_SAVENOT_Player_SUP[i] = (udg_SAVENOT_Player_Games[i] - (CodePart[3] + CodePart[4]))
        if (udg_SAVENOT_Player_Games[i] != udg_SAVE_Player_OFF[i] + udg_SAVE_Player_DEF[i] + udg_SAVENOT_Player_SUP[i]) then
            set udg_SAVE_Player_Wins[i] = 0
            set udg_SAVE_Player_Losses[i] = 0
            set udg_SAVENOT_Player_Games[i] = 0
            set udg_SAVENOT_Player_SUP[i] = 0
            set udg_SAVE_Player_OFF[i] = 0
            set udg_SAVE_Player_DEF[i] = 0
            call DisplayTextToPlayer(Player(i - 1), 0, 0, "|c00ff0000Invalid Load Code Entered|r")
            return
        endif
        if (udg_SAVENOT_Player_XP[i] >= 25) then
            call SetPlayerTechResearchedSwap( 'R001', 1, Player(i - 1) )
        endif
        if (CodePart[3] >= 5) then
            call SetPlayerTechResearchedSwap( 'R002', 1, Player(i - 1) )
        endif
        if (CodePart[4] >= 3) then
            call SetPlayerTechResearchedSwap( 'R003', 1, Player(i - 1) )
        endif
        if (udg_SAVENOT_Player_SUP[i] >= 2) then
            call SetPlayerTechResearchedSwap( 'R004', 1, Player(i - 1) )
        endif
        if (CodePart[3] >= 3) then
            call SetPlayerTechResearchedSwap( 'R005', 1, Player(i - 1) )
        endif
        set udg_Player_HasLoaded[GetConvertedPlayerId(GetTriggerPlayer())] = true
        call SetPlayerName( Player(i - 1), ( udg_RankName[getRank(udg_SAVENOT_Player_XP[i])] + ( " " + GetPlayerName(Player(i - 1)) ) ))
        call DisplayTextToPlayer(Player(i - 1), 0, 0, "|c00ffcc00Your Code has been loaded. Press ESC to view your profile.|r")
        call DisplayTextToPlayer(Player(i - 1), 0, 0, "|c00ffcc00Your Rank is: |r" + udg_RankName[getRank(udg_SAVENOT_Player_XP[i])] + " - [|c006969ffLevel " + I2S(getRank(udg_SAVENOT_Player_XP[i])) + "|r]")
        call MultiboardSetItemValueBJ( udg_ProfileBoard[i], 1, 1, ( "|c00ffcc00" + ( udg_RankName[getRank(udg_SAVENOT_Player_XP[i])] + "|r - [|c006969ffLevel " + I2S(getRank(udg_SAVENOT_Player_XP[i])) + "|r]" ) ) )
        call MultiboardSetItemValueBJ( udg_ProfileBoard[i], 2, 4, I2S(udg_SAVENOT_Player_XP[i]) )
        call MultiboardSetItemValueBJ( udg_ProfileBoard[i], 2, 5, I2S(udg_SAVENOT_Player_Games[i]) )
        call MultiboardSetItemValueBJ( udg_ProfileBoard[i], 2, 6, ( I2S(R2I(( ( I2R(udg_SAVE_Player_Wins[i]) / I2R(udg_SAVENOT_Player_Games[i]) ) * 100.00 ))) + "%" ) )
        call MultiboardSetItemValueBJ( udg_ProfileBoard[i], 2, 9, I2S(udg_SAVE_Player_OFF[i]) )
        call MultiboardSetItemValueBJ( udg_ProfileBoard[i], 2, 10, I2S(udg_SAVE_Player_DEF[i]) )
        call MultiboardSetItemValueBJ( udg_ProfileBoard[i], 2, 11, I2S(udg_SAVENOT_Player_SUP[i]) )
        if udg_Player_Hero[GetConvertedPlayerId(GetTriggerPlayer())] != null then
            if GetTrophy(GetTriggerPlayer()) != udg_ES_TROPHY_ID then
                set bagitem = CreateItem(udg_ES_Item[GetTrophy(GetTriggerPlayer())], 0, 0)
                call SetItemVisible(bagitem, false)
                call SetHandleHandle(udg_Player_Hero[GetConvertedPlayerId(GetTriggerPlayer())], "slot8", bagitem)
            endif
        endif
        set bagitem = null
    endfunction

    function Trig_Load_Code_Conditions takes nothing returns boolean
        if SubStringBJ(GetEventPlayerChatString(), 1, 5) != "-load" then
            return false
        endif
        if udg_Player_HasLoaded[GetConvertedPlayerId(GetTriggerPlayer())] == true then
            return false
        endif
        return true
    endfunction

    //===========================================================================
    function InitTrig_Load_Code takes nothing returns nothing
        set gg_trg_Load_Code = CreateTrigger(  )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(0), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(1), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(2), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(3), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(4), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(5), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(6), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(7), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(8), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(9), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(10), "-load", false )
        call TriggerRegisterPlayerChatEvent( gg_trg_Load_Code, Player(11), "-load", false )
        call TriggerAddCondition( gg_trg_Load_Code, Condition( function Trig_Load_Code_Conditions ) )
        call TriggerAddAction( gg_trg_Load_Code, function read50 )
    endfunction
     
     

    Attached Files: