- 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.
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:
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:
This will turn an integer into 6 boolean values:
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
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!--------
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
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
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
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