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

Save/Load Compression

Level 10
Joined
Jun 16, 2007
Messages
415
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:

JASS:
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:
JASS:
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:
JASS:
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


JASS:
////
//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
JASS:
///////
//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
 

Attachments

  • Advanced Wars Beta.w3x
    1.2 MB · Views: 823
Top