- Joined
- Jul 10, 2007
- Messages
- 6,306
Needs an update, will update some time soon
This guide is set up to get Encoder quickly installed into a map with support for backwards compatible codes using a design that was developed for Pearl East Kingdom.
The tutorial is aimed at beginners who know little to nothing at all about coding, or even the trigger editor in general, or even anything about map making. The tutorial does get more advanced in the case of actually building the save/load stuff, meaning that a semi advanced coder will be needed to actually do the structure of save/load. However, with this tutorial, a beginner who knows nothing about coding will be able to manage most of it, meaning that they will need as little as help as possible.
Setting Up Encoder
The very first required thing is NewGen WE. If it is not currently installed, be sure to get it: NewGen Tutorial. Be sure to familiarize yourself with the trigger editor at the NewGen Tutorial link.
Go through the links and put the resources at those links into the map. Be sure not to put resources into the map that are already in it (Table is a widely used resource).
Find the resource code in the first post. It should have library at the top of it ->
To put it into the map, create a new trigger in the trigger editor (ctrl+t), select that trigger (click it), go to the Edit drop down menu on the top left corner of the window and click on Convert to Custom Text. The trigger will now be in JASS code. Click somewhere in the JASS code window and hit CTRL+A, then hit Delete or Backspace. From here, go to a link and copy and paste the resource code into that trigger. This must be done for each link.
Create a new trigger and paste this code into it-
Next, if you would like to save experience or locations, you need to install the Lua Framework -> Quickstart Guide to Installing Lua Scripts
From here, copy and paste these resources into the map. Be sure to not copy and paste resources you already have (check first).
Lua installation script
Code
Next, paste Encoder into the map.
Finally, you will need a special save/load framework that supports backwards compatible codes-
Create a Catalogs category and create a Catalogs trigger and a Catalog1 trigger in it, then place this code
Catalog1
Catalogs
Create an Encoders category. Create an Encoder1 trigger, an Encoder Settings trigger and an Encoders trigger within it.
Encoder1
Encoder Settings
Encoders
Create a Loaders category and then create a Loader1 trigger and a SaveLoad trigger inside of it
Loader1
SaveLoad
Configuring Encoder
Go to the Encoder Settings trigger.
The first lines things that need to be modified are BASE and BASE_OFFLINE. These bases are the character sets the save/load codes will use. The more characters in the set, the smaller the code. A commonly used character set is a-z, A-Z, 0-9
These character sets can be scrambled up in order to make the code harder to crack
BASE refers to the online (b.net and LAN) character set and BASE_OFFLINE refers to the offline (single player games) character set. These two sets should be different.
Scramble the sets up so that they are hard to figure out. Also make the two character sets different. For example-
A very useful online scrambling tool- http://textmechanic.com/Word-Scrambler.html
Simply put the character set into the box and click "Scramble all text"
The SECURITY and SECURITY_OFFLINE values deal with checksums. Checksums are used to see if a code was modified or not. The larger the checksum, the more secure the code, but the longer the code. The checksums have to be bigger than 0.
Try to make the checksums hard to guess. A 6 digit checksum is generally a good bet well.
SECURITY deals with online checksums and SECURITY_OFFLINE deals with offline checksums. These two values should be different, and the offline security should be a bigger number than the online one.
A value of 359301 means that only 1 in 359301 codes will work.
Now go to the SaveLoad trigger and scroll down to SETTINGS.
The DELIMITER value refers to the character that separates the encoder version from the actual save/load code.
When loading a code, the encoder version can be passed in, which allows user to load older codes.
-load 1,aaaa-aaab
The above would load the code aaaa-aaab using encoder version 1.
In this case, a comma (",") is the DELIMITER.
The DELIMITER value can be changed to whatever, but a comma is recommended
The next value is INVALID_CODE, which refers to the message shown when a code is invalid. By default, this is Invalid Code in red letters.
Now scroll to the very bottom to
These functions are where the player's hero is created/removed. In many maps, a player's hero is stored into some sort of array or group and many values are initialized. When a player's hero is removed, they are also possibly removed from groups and so forth. Be sure to properly initialize the player's hero in CreateHero and properly remove it in RemoveHero.
Next, go to Scrambler and read information in Settings area. Modifying accordingly.
Next, go to the Encoder system and scroll down to SETTINGS.
VER_BASE refers to the character set used for encoder versions. This doesn't really need encryption and most people prefer a 10 character set of 0-9 so that they are easier to remember. Some people use a-z, A-Z, 0-9 to make the encoder versions smaller. These are used for backwards compatible codes (loading old codes).
The next thing is the coloring. Coloring colors numbers, lowercase letters, uppercase letters, special characters, and space characters (delimiters). They are used to make the code easier to read. The values below are considered good and very easy to read.
Farther down is the space settings (delimiter settings). This determines how often to put a space in the code and what type of space to use. Most save/load codes use dashes ("-") and space every 4 or 5 characters.
The final area is the Encryption settings.
The SHUFFLES value deals with code obsfucation (scrambling the code it up so that is garbled and unreadable). The value determines how many times the code should be shuffled. A value between 2 and 5 is typically good. This makes it so that very tiny changes to the saved data result in entirely different codes.
The CHECKSUM_VARIANCE value determines how many different bases are active. The larger the variance value, the smaller the range of active checksums. A small range means that it is more likely for two players to have the same checksums.
Smaller variance increases the range of active checksums, however it also increases the range of cehcksum strengths. This means that some checksums may be much weaker than others, which increases the chances for those players with weaker checksums to tamper with their code.
Checksum strength should be about the same for each checksum and there should be enough active checksums that it is unlikely that two players will have te same checksum.
.85 is a rather balanced value, but it can be increased for generally stronger checksums with smaller ranges or decreased for weaker checksums with wider ranges.
Examples
The PLAYER_CHECKSUM_SALT value is like a password that plays a role in the base for save/load the player gets. The base passed into Encoder when cerating an Encoder object is not the base that is used. Every player gets a different base which is actually a scrambled up version of the passed in base. The scrambling uses both the player name and PLAYER_CHECKSUM_SALT to come up with a scrambling key.
Now Encoder should be configured and you should be ready to start building your actual save/load code.
Building the Catalogs
When saving things like units or items, they need to be put into a catalog. Catalogs allow one to make very large values a lot smaller, which results in a much smaller code.
A save/load code can have many catalogs, and can have many different versions of catalogs (for backwards compatibility).
Go to the Catalogs trigger. In there, you will see
If you were to create a second catalog version set (for a new encoder), you would create a Catalog2 trigger and then instead of
These catalogs ensure that older codes are still able to be loaded into the map. If an older catalog is changed, those older codes will no longer work.
New catalogs can be added to allow for older and older codes to be saved (5 versions back, 10 versions back, however many are desired). You should not continue to add new catalogs. Eventually, you should start overwriting old catalogs. To do this, rather than adding a new catalog, you just replace the oldest catalog code with the new catalog code and change the catalog version to that old version.
If you were to have 5 catalog versions
1,2,3,4,5
Rather than adding another, you could overwrite 1, then 2, then 3, then 4, then 5, etc. Doing this means that the map will continue to be able to go back 5 versions.
With catalogs, many catalogs can be in a given set-
Furthermore, older sets can be used in new encoders. For example, if you didn't change anything in an older catalog, you don't need to cnp it over to a newer catalog.
Catalogs should be named after what they refer to. For example, a catalog that had heroes in it would be HeroCatalog1. The number at the end refers to the set the catalog belongs to.
A common set of catalogs would be this-
Adding values to a catalog is easy. To add an object, go to the object editor and find what you want to add (hero, item, w/e). Hit CTRL+D and you will then be able to see what are called the raw ascii values of those objects. For example, the human paladin is Hpal and a footman is hfoo.
All of the objects that are able to be saved should be added to the catalogs. For example, if we wanted to be able to save a paladin, a mountain king, and Alleria's Flute of Accuracy
Notice that I included a little note as to what each thing was. Always include little notes as to what each little thing is in the catalog!
There are many different types of catalogs. This tutorial covers them- Catalogs Tutorial
Keep in mind that the hardest part in advanced save/load is the catalogs. For example, Pearl East Kingdom has class specific items, meaning that its item catalog is very complex. It also has charged items, making it even more complex. For catalogs, if you have no idea how to do something, ask for help in the Triggers and Scripts section and an advanced coder will come along to help you design it.
Building an Encoder
The backwards compatibility on encoders is set up much the same way as the backwards compatibility on catalogs. Within the Encoders trigger, you will see this-
This is where new encoders are added. Inside of the Encoder1 trigger, you should see these wrapping it
So it is pretty much the exact same deal as catalogs. However, you will notice that the code in it is a bit different-
This portion has to be specially named
That refers to the first encoder object. Encoder2 would be the second, and etc-
And the textmacros are of course added to the encoder list-
The catalog versions and encoder versions should coincide. Catalog set 1 would be catalogs specifically for encoder version 1, etc. Again, catalogs can be reused in later encoder versions if they are the exact same. For example, if no items are added to the ItemCatalog and the map moves to Encoder2, then Encoder2 can use ItemCatalog1, no need to copy and paste it and then modify it.
Each Encoder object initially has to be built. Building them is extremely easy.
In BuilderEncoder, put the Encoder version number. For example, Encoder1 would be BuilderEncoder(1) and Encoder2 would be BuildEncoder(2).
From here, you have to actually piece the encoder object together.
Slots for storing values are created in Encoder objects via CodeRange objects. These CodeRange objects are essentially slots that can hold a specific range of numbers.
Visualization-
Encoder
-----[0-15]
-----[0-15]
-----[100-200]
-----[-593-1429]
The first slot would be able to hold values between 0 and 15. The next could hold between 0 and 15, then after is 100 to 200, and then -593 to 1429.
Encoder objects can have lots of slots. Example of an Encoder that has a slot for storing gold (remember gold can go from 0 to 1,000,000)
Encoder
----[0-1000000]
These slots are created with what are called CodeRange objects.
Think of an Encoder object as this skeleton that can have all of these slots added to it, this way when you do your saving and loading, you can read/write to these slots. This allows one to pretty much save whatever they want to save.
So looking back
That would create a CodeRange object called range that can hold values between 0 and 1.
Many CodeRange objects can be generated, and they can be named pretty much whatever-
It is rather simple to add a CodeRange to an Encoder-
Where range is the name of the CodeRange object. See above example.
Specific ranges within slots can be linked to other slots, and these links can be given custom ids. The ids are important as the links may or may not be used. For example, not all items have item charges, so the id can be used in saving and loading to see if the current open slot is an item charge slot.
An example
The linka method can also be used, which means link all. This essentially links the entire range.
Some interesting algorithms can be used when doing links. For example, when saving a 6 slot inventory, there are two ways to do it-
The above would link 6 items to a hero. However, if the heroes inventory is empty, this means that 6 empty slots end up being saved. Another smarter way to do this wold be-
hero -> item -> item2 -> item3 -> item4 -> item5 -> item6
The links between each item go between the range of 1 and 600. If an item is null (a value of 0), then the link isn't entered. This means that if an inventory was empty, only 1 item would be saved (initial link).
Notice that the lower bounds of the ranges are sometimes 0 or sometimes 1 for the catalogs (like heroes and items). A range starting at 0 means that the value can possibly be null, like an empty inventory (6 null items). Hero starts at 1 because a player will always have a hero, so this value can never ever be null.
Infinite values can also be saved-
That would essentially keep saving items until it comes across a null item. This can be useful when saving an unknown amount of a set of values.
Please note that when it comes to catalogs, a catalog count is usually used for the code ranges and sub range variables are used for the links-
Saving and Loading
Go to the SaveLoad trigger and head down to the Save function
Values are written into a buffer in saving. The buffer is created from the latest Encoder object.
The buffer expects the values that the Encoder object expects. For example-
With this Encoder object, the buffer would expect two values, the first being between 0 and 100 and the second being between 1 and 99.
This could be a possibility-
Another possibilty would be-
The reason not all of the items were written is because of that 0 and the way the links were set up.
The slot where the 0 was written to is item2. The link between item2 and item3 requires a value between 1 and 600. Because 0 isn't in that range, the code stops at item2. Another example using the same Encoder.
In this case, the 0 is written into item4. It is also possible that no 0s are written, meaning that the inventory is full.
In this case, all 6 items are written, no 0s.
Ids can also be used to see if slots exist. For example, saving item charges-
Saving and loading require coding knowledge.
If you noticed, there is only one Save function. The framework will always save using the latest Encoder version. However, if you look, the same concept for Catalogs and Encoders is used for loading, thus allowing one to load from older codes.
In the case of loading, a new Loader must be used for a new Encoder, even if that Loader is identical to the old one.
In the case of Loaders, values are read from the buffer, so buffer.read is used.
For example, reading from a code using this Encoder-
buffer.id may be used to check if slots exist or not as before-
Example of loading a hero
Quickstart Guide to Saving/Loading with Encoder
This guide is set up to get Encoder quickly installed into a map with support for backwards compatible codes using a design that was developed for Pearl East Kingdom.
The tutorial is aimed at beginners who know little to nothing at all about coding, or even the trigger editor in general, or even anything about map making. The tutorial does get more advanced in the case of actually building the save/load stuff, meaning that a semi advanced coder will be needed to actually do the structure of save/load. However, with this tutorial, a beginner who knows nothing about coding will be able to manage most of it, meaning that they will need as little as help as possible.
Setting Up Encoder
The very first required thing is NewGen WE. If it is not currently installed, be sure to get it: NewGen Tutorial. Be sure to familiarize yourself with the trigger editor at the NewGen Tutorial link.
Go through the links and put the resources at those links into the map. Be sure not to put resources into the map that are already in it (Table is a widely used resource).
Find the resource code in the first post. It should have library at the top of it ->
library
. For each link, only the resource code should be posted: the code with the library
at the very top.To put it into the map, create a new trigger in the trigger editor (ctrl+t), select that trigger (click it), go to the Edit drop down menu on the top left corner of the window and click on Convert to Custom Text. The trigger will now be in JASS code. Click somewhere in the JASS code window and hit CTRL+A, then hit Delete or Backspace. From here, go to a link and copy and paste the resource code into that trigger. This must be done for each link.
Create a new trigger and paste this code into it-
JASS:
library KnuthChecksum uses BigInt
function GetKnuthChecksum takes BigInt k, integer m returns integer
local BigInt c = k.copy()
local BigInt c2 = k.copy()
call c.add(3,0)
call c2.multiplyBig(c)
call c.destroy()
set c = c2.mod(m)
call c2.destroy()
return c
endfunction
endlibrary
library Scrambler uses BigInt
/*************************************************************************************
*
* SETTINGS
*
*************************************************************************************/
globals
/*************************************************************************************
*
* SALT
*
* Refers to what to append to the player's name when generating scrambler keys
* for each player. This is essentially like a password. It's impossible for another
* map to descramble a number without this password.
*
* This password can be any string, such as "7/ah+53~r\\ZZ"
*
*************************************************************************************/
private constant string SALT = ""
endglobals
/*************************************************************************************
*
* SetShuffleOrder
*
* Creates the shuffling algorithm using 5 different prime bases.
*
* Shuffling mixes a number up using a variety of bases to produce results rivaling
* top random number generators.
*
* Different bases ensure better mixes.
*
* Enabled Bases:
*
* 2
* Will shuffle number in groups of 1 bit
* 3
* Will shuffle number in groups of 1.58 bits
* 5
* Will shuffle number in groups of 2.32 bits
* 7
* Will shuffle in groups of 2.81 bits
* 11
* Will shuffle in groups of 3.46 bits
*
* Strategies:
*
* 1.
* shuffle by large1, shuffle by small1, shuffle by large2, etc
* 2.
* shuffle by small1, shuffle by large1, shuffle by small2, etc
* 3.
* shuffle by small1, shuffle by small2, shuffle by large1, shuffle by small3, etc
*
* Keep in mind that as fractional bits are shuffled, bits split/merge, meaning that the number
* can drastically change by becoming much smaller or much larger.
*
*
* Shuffle Array: so
*
* Ex:
*
* set so[0]=3 //first mix by 1.58 bits
* set so[1]=2 //second mix by 1 bit
* set so[2]=7 //third mix by 2.81 bits
* set so[3]=2 //fourth mix by 1 bit
*
* return 4 //return number of mixes
*
*************************************************************************************/
private keyword so
private function SetShuffleOrder takes nothing returns integer
/*************************************************************************************
*
* MIXES
*
* array: so
* bases: 2,3,5,7,11
*
*************************************************************************************/
set so[0]=5
set so[1]=2
set so[2]=3
set so[3]=11
set so[4]=2
set so[5]=7
set so[6]=3
/*************************************************************************************
*
* MIX COUNT
*
*************************************************************************************/
return 7
endfunction
/*************************************************************************************
*
* CODE
*
*************************************************************************************/
globals
private integer array ss
private boolean array se
private BigInt array d
private integer i
private integer dc
private integer k
private integer s1
private integer s2
private integer s3
private integer pid
private trigger mt=CreateTrigger()
private trigger dt=CreateTrigger()
private trigger st=CreateTrigger()
private trigger ut=CreateTrigger()
private BigInt bi
private Base array bs
private integer array so
private integer sc=0
endglobals
private function LNF takes BigInt int, boolean i0 returns nothing
set dc=0
if (i0) then
loop
set int = int.next
exitwhen int.end
set d[dc] = int
set dc = dc + 1
endloop
else
loop
set int = int.next
exitwhen int.next.end
set d[dc] = int
set dc = dc + 1
endloop
set int = int.next
endif
endfunction
private function LNB takes BigInt int, boolean i0 returns nothing
set dc=0
if (not i0) then
set int = int.previous
endif
loop
set int = int.previous
exitwhen int.end
set d[dc] = int
set dc = dc + 1
endloop
endfunction
private function FLP takes integer id, integer i2 returns nothing
loop
exitwhen i2 == 0
set s1 = dc
loop
exitwhen s1 == 0
set s1 = s1 - 1
if (se[k]) then
set k = ss[id]
else
set k = k + 1
endif
endloop
set i2 = i2 - 1
endloop
endfunction
function Scramble takes BigInt int, integer id, integer shuffles, Base bb, boolean i0 returns nothing
local Base b=int.base
set pid=id
set k=ss[id]
set i=shuffles
debug if (ss[id] != 0) then
if (b != bb) then
set int.base = bb
endif
call LNF(int,i0)
call TriggerEvaluate(st)
if (b != bb) then
set int.base = b
endif
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "SCRAMBLER ERROR: INVALID PLAYER " + I2S(id))
debug endif
endfunction
function Unscramble takes BigInt int, integer id, integer shuffles, integer bb, boolean i0 returns nothing
local Base b=int.base
set i=shuffles
set pid=id
set k=ss[id]
debug if (ss[id] != 0) then
if (b != bb) then
set int.base = bb
endif
call LNB(int,i0)
call FLP(id,shuffles)
call TriggerEvaluate(ut)
if (b != bb) then
set int.base = b
endif
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "SCRAMBLER ERROR: INVALID PLAYER " + I2S(id))
debug endif
endfunction
private function St takes nothing returns boolean
loop
exitwhen i == 0
set s1 = dc
loop
exitwhen s1 == 0 //exitwhen no more digits
set s1 = s1 - 1 //shift down as array ends at n-1
set s2 = s1-ss[k]
loop
exitwhen s2 >= 0
set s2 = dc+s2
endloop
set s3 = d[s2].digit
set d[s2].digit = d[s1].digit
set d[s1].digit = s3
if (se[k]) then
set k = ss[pid]
else
set k = k + 1
endif
endloop
set i = i - 1
endloop
return false
endfunction
private function Ut takes nothing returns boolean
loop
exitwhen i == 0
set s1 = dc
loop
exitwhen s1 == 0
set s1 = s1 - 1
set k = k - 1
if (ss[k] == 0) then
set k = ss[pid+12]
endif
set s2 = s1+ss[k]
loop
exitwhen s2 < dc
set s2 = s2-dc
endloop
set s3 = d[s2].digit
set d[s2].digit = d[s1].digit
set d[s1].digit = s3
endloop
set i = i - 1
endloop
return false
endfunction
private function Mt takes nothing returns boolean
local integer sh=0
set k=ss[pid]
loop
exitwhen sh==sc
set i=1
set bi.base=bs[so[sh]]
call LNF(bi,false)
call St()
set sh=sh+1
set k=ss[pid]
endloop
return false
endfunction
private function Dt takes nothing returns boolean
local integer sh=sc
set k=ss[pid]
loop
exitwhen sh==0
set sh=sh-1
set i=1
set bi.base=bs[so[sh]]
call LNB(bi,false)
call FLP(pid,1)
call Ut()
set k=ss[pid]
endloop
return false
endfunction
function Shuffle takes BigInt int, integer id, integer h returns nothing
local Base b=int.base
debug if (ss[id] != 0) then
set bi=int
set pid=id
loop
exitwhen h==0
call TriggerEvaluate(mt)
set h=h-1
endloop
set int.base=b
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "SCRAMBLER ERROR: INVALID PLAYER " + I2S(id))
debug endif
endfunction
function Unshuffle takes BigInt int, integer id, integer h returns nothing
local Base b=int.base
debug if (ss[id] != 0) then
set bi=int
set pid=id
loop
exitwhen h==0
call TriggerEvaluate(dt)
set h=h-1
endloop
set int.base=b
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "SCRAMBLER ERROR: INVALID PLAYER " + I2S(id))
debug endif
endfunction
private module Init
private static method onInit takes nothing returns nothing
local integer is = 11
local integer hh
local integer ks = 25
local Base b8 = Base["012345678"]
local BigInt bg
call TriggerAddCondition(mt,Condition(function Mt))
call TriggerAddCondition(dt,Condition(function Dt))
call TriggerAddCondition(st,Condition(function St))
call TriggerAddCondition(ut,Condition(function Ut))
set bs[2]=Base["01"]
set bs[3]=Base["012"]
set bs[5]=Base["01234"]
set bs[7]=Base["0123456"]
set bs[11]=Base["0123456789A"]
set sc=SetShuffleOrder()
loop
if (GetPlayerSlotState(Player(is)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(is)) == MAP_CONTROL_USER) then
set ss[is] = ks
set hh = StringHash(StringCase(GetPlayerName(Player(is))+SALT, false))
if (hh < 0) then
set hh = -hh
endif
set bg = BigInt.create(b8)
call bg.add(hh,0)
set bg = bg.previous
loop
set ss[ks] = bg.digit + 1
set bg = bg.previous
exitwhen bg.end
set ks = ks + 1
endloop
set se[ks] = true
set ss[is+12] = ks
call bg.destroy()
set ks = ks + 2
endif
exitwhen is == 0
set is = is - 1
endloop
endmethod
endmodule
private struct Inits extends array
implement Init
endstruct
endlibrary
Next, if you would like to save experience or locations, you need to install the Lua Framework -> Quickstart Guide to Installing Lua Scripts
From here, copy and paste these resources into the map. Be sure to not copy and paste resources you already have (check first).
JASS:
library WorldBounds
private module WorldBoundInit
private static method onInit takes nothing returns nothing
set world = GetWorldBounds()
set maxX = GetRectMaxX(world)
set maxY = GetRectMaxY(world)
set minX = GetRectMinX(world)
set minY = GetRectMinY(world)
set centerX = (maxX+minX)/2
set centerY = (minY+maxY)/2
set worldRegion = CreateRegion()
call RegionAddRect(worldRegion, world)
endmethod
endmodule
struct WorldBounds extends array
readonly static real maxX
readonly static real maxY
readonly static real minX
readonly static real minY
readonly static real centerX
readonly static real centerY
readonly static rect world
readonly static region worldRegion
implement WorldBoundInit
endstruct
endlibrary
Lua installation script
JASS:
//! externalblock extension=lua ObjectMerger $FILENAME$
//! runtextmacro LUA_FILE_HEADER()
//! i dofile("GetVarObject")
//! i local id = getvarobject("Hvsh", "units", "UNITS_UNIT_STATE_PERCENT", true)
//! i createobject("Hvsh", id)
//! i makechange(current, "uabi", "Aloc,Avul")
//! i makechange(current, "usid", "0")
//! i makechange(current, "usin", "0")
//! i makechange(current, "usca", "0")
//! i updateobjects()
//! endexternalblock
Code
JASS:
library UnitStatePercent uses WorldBounds/* v1.0.1.5
globals
private real xl = 0
private real yl = 0
private unit h = null
endglobals
private module Init
private static method onInit takes nothing returns nothing
set xl = WorldBounds.maxX-WorldBounds.minX
set yl = WorldBounds.maxY-WorldBounds.minY
set h = CreateUnit(Player(15), UNITS_UNIT_STATE_PERCENT, WorldBounds.maxX, WorldBounds.maxY, 0)
call SetUnitX(h, WorldBounds.maxX)
call SetUnitY(h, WorldBounds.maxY)
call PauseUnit(h, true)
endmethod
endmodule
private struct Inits extends array
implement Init
endstruct
function GetPercentHeroXP takes unit u returns integer
local integer l=GetHeroLevel(u)
local integer m=0
if (l>1) then
call SetHeroLevel(h,l,false)
set m=GetHeroXP(h)
endif
call SetHeroLevel(h,l+1,false)
set m=(GetHeroXP(u)-m)*100/(GetHeroXP(h)-m)
call UnitStripHeroLevel(h,l+1)
return m
endfunction
function AddPercentHeroXP takes unit u,integer p returns nothing
local integer l
local integer m=0
if (p>0) then
set l=GetHeroLevel(u)
if (l>1) then
call SetHeroLevel(h,l,false)
set m=GetHeroXP(h)
endif
call SetHeroLevel(h,l+1,false)
call AddHeroXP(u,R2I((GetHeroXP(h)-m)*p/100.),false)
call UnitStripHeroLevel(h,l+1)
endif
endfunction
function GetPercentUnitLife takes unit u returns integer
return R2I((GetWidgetLife(u)/GetUnitState(u, UNIT_STATE_MAX_LIFE))*100+.5)
endfunction
function SetPercentUnitLife takes unit u, integer p returns nothing
call SetWidgetLife(u,p/100.*GetUnitState(u, UNIT_STATE_MAX_LIFE))
endfunction
function GetPercentUnitMana takes unit u returns integer
local real m = GetUnitState(u, UNIT_STATE_MAX_MANA)
if (m == 0) then
return 0
endif
return R2I(GetUnitState(u, UNIT_STATE_MANA)/m*100+.5)
endfunction
function SetPercentUnitMana takes unit u, integer p returns nothing
call SetUnitState(u, UNIT_STATE_MANA, p/100.*GetUnitState(u, UNIT_STATE_MAX_MANA))
endfunction
function GetPercentUnitX takes unit u returns integer
return R2I((GetWidgetX(u)-WorldBounds.minX)/xl*100+.5)
endfunction
function PercentToX takes integer l returns real
return l/100.*xl+WorldBounds.minX
endfunction
function GetPercentUnitY takes unit u returns integer
return R2I((GetWidgetY(u)-WorldBounds.minY)/yl*100+.5)
endfunction
function PercentToY takes integer l returns real
return l/100.*yl+WorldBounds.minY
endfunction
function GetPercentUnitFacing takes unit u returns integer
local integer i = R2I(GetUnitFacing(u)/360.*100+.5)
if (i == 100) then
return 0
endif
return i
endfunction
function PercentToFacing takes integer f returns real
return f/100.*360
endfunction
endlibrary
Next, paste Encoder into the map.
Finally, you will need a special save/load framework that supports backwards compatible codes-
Create a Catalogs category and create a Catalogs trigger and a Catalog1 trigger in it, then place this code
Catalog1
JASS:
//! textmacro CATALOG_1
struct Catalog1 extends array
implement Catalog
private static method onInit takes nothing returns nothing
endmethod
endstruct
//! endtextmacro
Catalogs
JASS:
library Catalogs uses Catalog
//! runtextmacro CATALOG_1()
endlibrary
Create an Encoders category. Create an Encoder1 trigger, an Encoder Settings trigger and an Encoders trigger within it.
Encoder1
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange range = CodeRange.create(0, 1)
call encoder.add(range)
endfunction
//! endtextmacro
Encoder Settings
JASS:
//! textmacro ENCODER_SETTINGS
globals
private constant string BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
private constant string BASE_OFFLINE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
private constant integer SECURITY = 111
private constant integer SECURITY_OFFLINE = 111
private constant integer ENCODER_VER = 1
private constant integer ENCODER_COUNT = 1
private constant integer MIN_CODE_LENGTH=12
///////////////////////////////////////////////////////////////////////////
//
// IGNORE EVERYTHING BELOW HERE
//
///////////////////////////////////////////////////////////////////////////
private integer curEncoder = 0
private boolean online = false
private integer array encoders
private integer array encoderVer
endglobals
//! endtextmacro
Encoders
JASS:
library Encoders uses Catalogs, Encoder
//! runtextmacro ENCODER_SETTINGS()
//! runtextmacro ENCODER_CODE()
///////////////////////////////////////////////////////////////////////////
//
// ADD NEW ENCODERS HERE
//
///////////////////////////////////////////////////////////////////////////
//! runtextmacro ENCODER_1()
///////////////////////////////////////////////////////////////////////////
//
// IGNORE EVERYTHING BELOW HERE
//
///////////////////////////////////////////////////////////////////////////
private struct EncodersInit extends array
private static method onInit takes nothing returns nothing
local integer i = ENCODER_COUNT
set online = not ReloadGameCachesFromDisk()
if (ENCODER_COUNT > 0) then
loop
exitwhen i == 0
call ExecuteFunc(SCOPE_PRIVATE + "Encoder" + I2S(i))
set i = i - 1
endloop
endif
endmethod
endstruct
//! textmacro ENCODER_CODE
function GetCurrentEncoder takes nothing returns Encoder
return curEncoder
endfunction
function GetEncoder takes integer ver returns integer
return encoders[ver]
endfunction
function GetEncoderVer takes integer id returns integer
return encoderVer[id]
endfunction
private function BuildEncoder takes integer ver returns Encoder
local Encoder encoder
if (online) then
set encoder = Encoder.create(BASE, SECURITY, ver)
else
set encoder = Encoder.create(BASE_OFFLINE, SECURITY_OFFLINE, ver)
endif
set encoders[ver] = encoder
set encoderVer[encoder] = ver
if (ver == ENCODER_VER) then
set curEncoder = encoder
endif
return encoder
endfunction
//! endtextmacro
endlibrary
Create a Loaders category and then create a Loader1 trigger and a SaveLoad trigger inside of it
Loader1
JASS:
//! textmacro LOAD_1
private function Load1 takes nothing returns nothing
local player triggerPlayer = GetTriggerPlayer()
local integer playerId = GetPlayerId(triggerPlayer)
local DataBuffer buffer = bf
endfunction
//! endtextmacro
SaveLoad
JASS:
library SaveLoad uses Catalogs, Encoders
///////////////////////////////////////////////////////////////////////////
//
// Loader takes a load code and opens a data buffer with it
//
// if it returns 0, code was invalid
//
// expects
// -load ver,code
// -load code
//
///////////////////////////////////////////////////////////////////////////
//
// SETTINGS
//
///////////////////////////////////////////////////////////////////////////
globals
private constant string DELIMITER = ","
private constant string INVALID_CODE = "|cffff0000Invalid Code"
endglobals
//! runtextmacro SAVE_LOAD_CODE()
///////////////////////////////////////////////////////////////////////////
//
// ADD NEW ENCODERS HERE
//
///////////////////////////////////////////////////////////////////////////
//! runtextmacro LOAD_1()
private function Save takes nothing returns boolean
local player triggerPlayer = GetTriggerPlayer()
local integer playerId = GetPlayerId(triggerPlayer)
local DataBuffer buffer = GetCurrentEncoder().write(playerId)
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, "Encoder Version: " + GetCurrentEncoder().toString())
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, buffer.code)
return false
endfunction
///////////////////////////////////////////////////////////////////////////
//
// IGNORE BETWEEN
//
///////////////////////////////////////////////////////////////////////////
private function Loader takes nothing returns boolean
local string s = GetEventPlayerChatString()
local string c = ""
local integer i
local integer q
local string s2
local integer pid = GetPlayerId(GetTriggerPlayer())
local Encoder enc
set s = SubString(s, 6, StringLength(s))
set q = StringLength(s)
if (not loaded[pid]) then
if (q >= MIN_CODE_LENGTH) then
set s2 = ""
set i = 0
loop
exitwhen i == q
set c = SubString(s, i, i+1)
exitwhen c == DELIMITER
if (c != " ") then
set s2 = s2 + c
endif
set i = i + 1
endloop
if (i < q) then
set enc = Encoder.convertString(SubString(s2, 0, i))
set s=SubString(s, i+1, StringLength(s)
if (StringLength(s)>=MIN_CODE_LENGTH) then
set bf = enc.read(s), GetPlayerId(GetTriggerPlayer()))
endif
elseif (StringLength(s2)>=MIN_CODE_LENGTH) then
set enc = GetCurrentEncoder()
set bf = enc.read(s2, GetPlayerId(GetTriggerPlayer()))
endif
else
set bf = 0
endif
if (bf != 0) then
set loaded[pid] = true
call ExecuteFunc(SCOPE_PRIVATE + "Load" + I2S(GetEncoderVer(enc)))
else
call DisplayTimedTextToPlayer(GetTriggerPlayer(), 0, 0, 60, INVALID_CODE)
endif
endif
return false
endfunction
private struct SaveLoad extends array
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local trigger t2 = CreateTrigger()
local integer i = 11
loop
if (GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(i)) == MAP_CONTROL_USER) then
call TriggerRegisterPlayerChatEvent(t, Player(i), "-save", true)
call TriggerRegisterPlayerChatEvent(t2, Player(i), "-load", false)
endif
exitwhen i == 0
set i = i - 1
endloop
call TriggerAddCondition(t, Condition(function Save))
call TriggerAddCondition(t2, Condition(function Loader))
set t = null
set t2 = null
endmethod
endstruct
//! textmacro SAVE_LOAD_CODE
globals
private boolean array loaded
private DataBuffer bf
endglobals
///////////////////////////////////////////////////////////////////////////
//
// END IGNORE BETWEEN
//
///////////////////////////////////////////////////////////////////////////
private function RemoveHero takes player triggerPlayer returns nothing
endfunction
private function CreateHero takes integer unitTypeId, player triggerPlayer returns unit
endfunction
//! endtextmacro
endlibrary
Configuring Encoder
Go to the Encoder Settings trigger.
The first lines things that need to be modified are BASE and BASE_OFFLINE. These bases are the character sets the save/load codes will use. The more characters in the set, the smaller the code. A commonly used character set is a-z, A-Z, 0-9
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
These character sets can be scrambled up in order to make the code harder to crack
"abhiLMNjklmnopqryzABCDE56FGHstuIJKOPQRcdefgSTUVvwxWXYZ01234789"
BASE refers to the online (b.net and LAN) character set and BASE_OFFLINE refers to the offline (single player games) character set. These two sets should be different.
Scramble the sets up so that they are hard to figure out. Also make the two character sets different. For example-
JASS:
private constant string BASE = "JsiTWrPeQ8g5UNXvub9yw4MztxRjCf3q1YoSZFO7HEk62LImVadGcBKpD0Ahln"
private constant string BASE_OFFLINE = "5vrPu9bJ4MwytiUjTXs8f3zNxCWgReQIq1YGSodmZaVBF6clk7OnAhEHLK0Dp2"
A very useful online scrambling tool- http://textmechanic.com/Word-Scrambler.html
Simply put the character set into the box and click "Scramble all text"
The SECURITY and SECURITY_OFFLINE values deal with checksums. Checksums are used to see if a code was modified or not. The larger the checksum, the more secure the code, but the longer the code. The checksums have to be bigger than 0.
Try to make the checksums hard to guess. A 6 digit checksum is generally a good bet well.
SECURITY deals with online checksums and SECURITY_OFFLINE deals with offline checksums. These two values should be different, and the offline security should be a bigger number than the online one.
A value of 359301 means that only 1 in 359301 codes will work.
JASS:
private constant integer SECURITY = 359301
private constant integer SECURITY_OFFLINE = 694632
Now go to the SaveLoad trigger and scroll down to SETTINGS.
The DELIMITER value refers to the character that separates the encoder version from the actual save/load code.
When loading a code, the encoder version can be passed in, which allows user to load older codes.
-load 1,aaaa-aaab
The above would load the code aaaa-aaab using encoder version 1.
In this case, a comma (",") is the DELIMITER.
The DELIMITER value can be changed to whatever, but a comma is recommended
private constant string DELIMITER = ","
The next value is INVALID_CODE, which refers to the message shown when a code is invalid. By default, this is Invalid Code in red letters.
private constant string INVALID_CODE = "|cffff0000Invalid Code"
Now scroll to the very bottom to
JASS:
private function RemoveHero takes player triggerPlayer returns nothing
endfunction
private function CreateHero takes integer unitTypeId, player triggerPlayer returns unit
endfunction
These functions are where the player's hero is created/removed. In many maps, a player's hero is stored into some sort of array or group and many values are initialized. When a player's hero is removed, they are also possibly removed from groups and so forth. Be sure to properly initialize the player's hero in CreateHero and properly remove it in RemoveHero.
Next, go to Scrambler and read information in Settings area. Modifying accordingly.
Next, go to the Encoder system and scroll down to SETTINGS.
VER_BASE refers to the character set used for encoder versions. This doesn't really need encryption and most people prefer a 10 character set of 0-9 so that they are easier to remember. Some people use a-z, A-Z, 0-9 to make the encoder versions smaller. These are used for backwards compatible codes (loading old codes).
private constant string VER_BASE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
The next thing is the coloring. Coloring colors numbers, lowercase letters, uppercase letters, special characters, and space characters (delimiters). They are used to make the code easier to read. The values below are considered good and very easy to read.
JASS:
private constant string NUM_COLOR = "|cff40e0d0" //what to color numbers
private constant string LOWER_COLOR = "|cffff69b4" //what to color lowercase characters
private constant string UPPER_COLOR = "|cff00AA00" //what to color uppercase characters
private constant string SPEC_COLOR = "|cffffff00" //what to color special characters
private constant string SPACE_COLOR = "|cffffffff" //what to color space characters
Farther down is the space settings (delimiter settings). This determines how often to put a space in the code and what type of space to use. Most save/load codes use dashes ("-") and space every 4 or 5 characters.
JASS:
private constant string DELIMITER = "-" //space to make code easier to read
private constant integer DELIMITER_COUNT = 4 //how many characters per delimiter
The final area is the Encryption settings.
JASS:
private constant integer SHUFFLES = 2
private constant real CHECKSUM_VARIANCE=.85
private constant string PLAYER_CHECKSUM_SALT=""
The SHUFFLES value deals with code obsfucation (scrambling the code it up so that is garbled and unreadable). The value determines how many times the code should be shuffled. A value between 2 and 5 is typically good. This makes it so that very tiny changes to the saved data result in entirely different codes.
private constant integer SHUFFLES = 2
The CHECKSUM_VARIANCE value determines how many different bases are active. The larger the variance value, the smaller the range of active checksums. A small range means that it is more likely for two players to have the same checksums.
Smaller variance increases the range of active checksums, however it also increases the range of cehcksum strengths. This means that some checksums may be much weaker than others, which increases the chances for those players with weaker checksums to tamper with their code.
Checksum strength should be about the same for each checksum and there should be enough active checksums that it is unlikely that two players will have te same checksum.
.85 is a rather balanced value, but it can be increased for generally stronger checksums with smaller ranges or decreased for weaker checksums with wider ranges.
Examples
Code:
.85 for a checksum of 238,609,294
min: 202,817,899
active checksums: 35,791,395
1 in 35,791,395 checksums will work for a player and checksums will all be around same strength.
Code:
.99 for a checksum of 238,609,294
min: 236,223,201
active checksums: 2,386,093
1 in 2,386,093 checksums will work for a player and checksums will all be around same strength.
Code:
.01 for a checksum of 238,609,294
min: 2,386,092
active checksums: 236,223,202
1 in 236,223,202 will work for a player and checksums will have a wide range of strengths from weak to strong.
private constant real CHECKSUM_VARIANCE=.85
The PLAYER_CHECKSUM_SALT value is like a password that plays a role in the base for save/load the player gets. The base passed into Encoder when cerating an Encoder object is not the base that is used. Every player gets a different base which is actually a scrambled up version of the passed in base. The scrambling uses both the player name and PLAYER_CHECKSUM_SALT to come up with a scrambling key.
private constant string PLAYER_CHECKSUM_SALT=""
Now Encoder should be configured and you should be ready to start building your actual save/load code.
Building the Catalogs
When saving things like units or items, they need to be put into a catalog. Catalogs allow one to make very large values a lot smaller, which results in a much smaller code.
A save/load code can have many catalogs, and can have many different versions of catalogs (for backwards compatibility).
Go to the Catalogs trigger. In there, you will see
//! runtextmacro CATALOG_1()
. This is used to import version 1 of all of the catalogs (all catalogs associated with the first encoder version). Look inside of the Catalog1 trigger. You will notice that it is surrounded by
JASS:
//! textmacro CATALOG_1
//! endtextmacro
If you were to create a second catalog version set (for a new encoder), you would create a Catalog2 trigger and then instead of
//! textmacro CATALOG_1
, you'd have //! textmacro CATALOG_2
. This would also be added to the Catalogs trigger-
JASS:
library Catalogs uses Catalog
//! runtextmacro CATALOG_1()
//! runtextmacro CATALOG_2()
endlibrary
These catalogs ensure that older codes are still able to be loaded into the map. If an older catalog is changed, those older codes will no longer work.
New catalogs can be added to allow for older and older codes to be saved (5 versions back, 10 versions back, however many are desired). You should not continue to add new catalogs. Eventually, you should start overwriting old catalogs. To do this, rather than adding a new catalog, you just replace the oldest catalog code with the new catalog code and change the catalog version to that old version.
If you were to have 5 catalog versions
1,2,3,4,5
Rather than adding another, you could overwrite 1, then 2, then 3, then 4, then 5, etc. Doing this means that the map will continue to be able to go back 5 versions.
With catalogs, many catalogs can be in a given set-
JASS:
//! textmacro CATALOG_1
struct Catalog1 extends array
implement Catalog
private static method onInit takes nothing returns nothing
endmethod
endstruct
struct Catalogs1 extends array
implement Catalog
private static method onInit takes nothing returns nothing
endmethod
endstruct
struct Catalogss1 extends array
implement Catalog
private static method onInit takes nothing returns nothing
endmethod
endstruct
//! endtextmacro
Furthermore, older sets can be used in new encoders. For example, if you didn't change anything in an older catalog, you don't need to cnp it over to a newer catalog.
Catalogs should be named after what they refer to. For example, a catalog that had heroes in it would be HeroCatalog1. The number at the end refers to the set the catalog belongs to.
A common set of catalogs would be this-
JASS:
//! textmacro CATALOG_1
struct HeroCatalog1 extends array
implement Catalog
private static method onInit takes nothing returns nothing
endmethod
endstruct
struct ItemCatalog1 extends array
implement Catalog
private static method onInit takes nothing returns nothing
endmethod
endstruct
//! endtextmacro
Adding values to a catalog is easy. To add an object, go to the object editor and find what you want to add (hero, item, w/e). Hit CTRL+D and you will then be able to see what are called the raw ascii values of those objects. For example, the human paladin is Hpal and a footman is hfoo.
All of the objects that are able to be saved should be added to the catalogs. For example, if we wanted to be able to save a paladin, a mountain king, and Alleria's Flute of Accuracy
JASS:
//! textmacro CATALOG_1
struct HeroCatalog1 extends array
implement Catalog
private static method onInit takes nothing returns nothing
call add('Hpal') //paladin
call add('Hmkg') //mountain king
endmethod
endstruct
struct ItemCatalog1 extends array
implement Catalog
private static method onInit takes nothing returns nothing
call add('afac') //Allerica's Flute of Accuracy
endmethod
endstruct
//! endtextmacro
Notice that I included a little note as to what each thing was. Always include little notes as to what each little thing is in the catalog!
There are many different types of catalogs. This tutorial covers them- Catalogs Tutorial
Keep in mind that the hardest part in advanced save/load is the catalogs. For example, Pearl East Kingdom has class specific items, meaning that its item catalog is very complex. It also has charged items, making it even more complex. For catalogs, if you have no idea how to do something, ask for help in the Triggers and Scripts section and an advanced coder will come along to help you design it.
Building an Encoder
The backwards compatibility on encoders is set up much the same way as the backwards compatibility on catalogs. Within the Encoders trigger, you will see this-
JASS:
///////////////////////////////////////////////////////////////////////////
//
// ADD NEW ENCODERS HERE
//
///////////////////////////////////////////////////////////////////////////
//! runtextmacro ENCODER_1()
This is where new encoders are added. Inside of the Encoder1 trigger, you should see these wrapping it
JASS:
//! textmacro ENCODER_1
//! endtextmacro
So it is pretty much the exact same deal as catalogs. However, you will notice that the code in it is a bit different-
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange range = CodeRange.create(0, 1)
call encoder.add(range)
endfunction
//! endtextmacro
This portion has to be specially named
private function Encoder1 takes nothing returns nothing
That refers to the first encoder object. Encoder2 would be the second, and etc-
JASS:
//! textmacro ENCODER_2
private function Encoder2 takes nothing returns nothing
local Encoder encoder = BuildEncoder(2) //version 2
local CodeRange range = CodeRange.create(0, 1)
call encoder.add(range)
endfunction
//! endtextmacro
JASS:
//! textmacro ENCODER_3
private function Encoder3 takes nothing returns nothing
local Encoder encoder = BuildEncoder(3) //version 3
local CodeRange range = CodeRange.create(0, 1)
call encoder.add(range)
endfunction
//! endtextmacro
And the textmacros are of course added to the encoder list-
JASS:
///////////////////////////////////////////////////////////////////////////
//
// ADD NEW ENCODERS HERE
//
///////////////////////////////////////////////////////////////////////////
//! runtextmacro ENCODER_1()
//! runtextmacro ENCODER_2()
//! runtextmacro ENCODER_3()
The catalog versions and encoder versions should coincide. Catalog set 1 would be catalogs specifically for encoder version 1, etc. Again, catalogs can be reused in later encoder versions if they are the exact same. For example, if no items are added to the ItemCatalog and the map moves to Encoder2, then Encoder2 can use ItemCatalog1, no need to copy and paste it and then modify it.
Each Encoder object initially has to be built. Building them is extremely easy.
local Encoder encoder = BuildEncoder(1)
In BuilderEncoder, put the Encoder version number. For example, Encoder1 would be BuilderEncoder(1) and Encoder2 would be BuildEncoder(2).
From here, you have to actually piece the encoder object together.
Slots for storing values are created in Encoder objects via CodeRange objects. These CodeRange objects are essentially slots that can hold a specific range of numbers.
Visualization-
Encoder
-----[0-15]
-----[0-15]
-----[100-200]
-----[-593-1429]
The first slot would be able to hold values between 0 and 15. The next could hold between 0 and 15, then after is 100 to 200, and then -593 to 1429.
Encoder objects can have lots of slots. Example of an Encoder that has a slot for storing gold (remember gold can go from 0 to 1,000,000)
Encoder
----[0-1000000]
These slots are created with what are called CodeRange objects.
Think of an Encoder object as this skeleton that can have all of these slots added to it, this way when you do your saving and loading, you can read/write to these slots. This allows one to pretty much save whatever they want to save.
So looking back
local CodeRange range = CodeRange.create(0, 1)
That would create a CodeRange object called range that can hold values between 0 and 1.
Many CodeRange objects can be generated, and they can be named pretty much whatever-
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange gold = CodeRange.create(0, 1000000)
local CodeRange lumber = CodeRange.create(0, 1000000)
call encoder.add(gold)
call encoder.add(lumber)
endfunction
//! endtextmacro
It is rather simple to add a CodeRange to an Encoder-
call encoder.add(range)
Where range is the name of the CodeRange object. See above example.
Specific ranges within slots can be linked to other slots, and these links can be given custom ids. The ids are important as the links may or may not be used. For example, not all items have item charges, so the id can be used in saving and loading to see if the current open slot is an item charge slot.
An example
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange item = CodeRange.create(0, 100)
local CodeRange itemCharge = CodeRange.create(1, 99)
//low bound, high bound, slot to be linked to, custom id
// 1 15 itemCharge 1
call item.link(1, 15, itemCharge, 1) //link items between 1 and 15 to itemCharge
//this means that items between 1 and 15 get
//an item charge.
call encoder.add(item)
//item is linked to itemCharge, so itemCharge isn't added
endfunction
//! endtextmacro
The linka method can also be used, which means link all. This essentially links the entire range.
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange item = CodeRange.create(0, 100)
local CodeRange itemCharge = CodeRange.create(1, 99)
//slot to be linked to
// itemCharge
call item.linka(itemCharge) //all items get an item charge
call encoder.add(item)
endfunction
//! endtextmacro
Some interesting algorithms can be used when doing links. For example, when saving a 6 slot inventory, there are two ways to do it-
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange hero = CodeRange.create(1, 25)
local CodeRange item = CodeRange.create(0, 600)
call hero.linka(item)
call hero.linka(item)
call hero.linka(item)
call hero.linka(item)
call hero.linka(item)
call hero.linka(item)
call encoder.add(hero)
endfunction
//! endtextmacro
The above would link 6 items to a hero. However, if the heroes inventory is empty, this means that 6 empty slots end up being saved. Another smarter way to do this wold be-
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange hero = CodeRange.create(1, 25)
local CodeRange item = CodeRange.create(0, 600)
local CodeRange item2 = CodeRange.create(0, 600)
local CodeRange item3 = CodeRange.create(0, 600)
local CodeRange item4 = CodeRange.create(0, 600)
local CodeRange item5 = CodeRange.create(0, 600)
local CodeRange item6 = CodeRange.create(0, 600)
call item.link(1,600,item2,1)
call item2.link(1,600,item3,1)
call item3.link(1,600,item4,1)
call item4.link(1,600,item5,1)
call item5.link(1,600,item6,1)
call hero.linka(item)
call encoder.add(hero)
endfunction
//! endtextmacro
hero -> item -> item2 -> item3 -> item4 -> item5 -> item6
The links between each item go between the range of 1 and 600. If an item is null (a value of 0), then the link isn't entered. This means that if an inventory was empty, only 1 item would be saved (initial link).
Notice that the lower bounds of the ranges are sometimes 0 or sometimes 1 for the catalogs (like heroes and items). A range starting at 0 means that the value can possibly be null, like an empty inventory (6 null items). Hero starts at 1 because a player will always have a hero, so this value can never ever be null.
Infinite values can also be saved-
call item.link(1,100,item,1)
That would essentially keep saving items until it comes across a null item. This can be useful when saving an unknown amount of a set of values.
Please note that when it comes to catalogs, a catalog count is usually used for the code ranges and sub range variables are used for the links-
JASS:
local CodeRange hero = CodeRange.create(0, HeroCatalog1.count)
local CodeRange items = CodeRange.create(0, ItemCatalog1.count)
local CodeRange items2 = CodeRange.create(0, ItemCatalog1_2.count)
call hero.link(HeroCatalog1.HERO_1_0, HeroCatalog1.HERO_1_1, items, 0)
call hero.link(HeroCatalog1.HERO_2_0, HeroCatalog1.HERO_2_1, items2, 0)
Saving and Loading
Go to the SaveLoad trigger and head down to the Save function
JASS:
private function Save takes nothing returns boolean
local player triggerPlayer = GetTriggerPlayer()
local integer playerId = GetPlayerId(triggerPlayer)
local DataBuffer buffer = GetCurrentEncoder().write(playerId)
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, "Encoder Version: " + GetCurrentEncoder().toString())
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, buffer.code)
return false
endfunction
Values are written into a buffer in saving. The buffer is created from the latest Encoder object.
The buffer expects the values that the Encoder object expects. For example-
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange item = CodeRange.create(0, 100)
local CodeRange itemCharge = CodeRange.create(1, 99)
//slot to be linked to
// itemCharge
call item.linka(itemCharge) //all items get an item charge
call encoder.add(item)
endfunction
//! endtextmacro
With this Encoder object, the buffer would expect two values, the first being between 0 and 100 and the second being between 1 and 99.
This could be a possibility-
JASS:
private function Save takes nothing returns boolean
local player triggerPlayer = GetTriggerPlayer()
local integer playerId = GetPlayerId(triggerPlayer)
local DataBuffer buffer = GetCurrentEncoder().write(playerId)
call buffer.write(50) //the item, between 0 and 100
call buffer.write(30) //the item charge, between 1 and 99
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, "Encoder Version: " + GetCurrentEncoder().toString())
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, buffer.code)
return false
endfunction
Another possibilty would be-
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange hero = CodeRange.create(1, 25)
local CodeRange item = CodeRange.create(0, 600)
local CodeRange item2 = CodeRange.create(0, 600)
local CodeRange item3 = CodeRange.create(0, 600)
local CodeRange item4 = CodeRange.create(0, 600)
local CodeRange item5 = CodeRange.create(0, 600)
local CodeRange item6 = CodeRange.create(0, 600)
call item.link(1,600,item2,1)
call item2.link(1,600,item3,1)
call item3.link(1,600,item4,1)
call item4.link(1,600,item5,1)
call item5.link(1,600,item6,1)
call hero.linka(item)
call encoder.add(hero)
endfunction
//! endtextmacro
JASS:
private function Save takes nothing returns boolean
local player triggerPlayer = GetTriggerPlayer()
local integer playerId = GetPlayerId(triggerPlayer)
local DataBuffer buffer = GetCurrentEncoder().write(playerId)
call buffer.write(12) //the hero, between 1 and 25)
call buffer.write(32) //the item, between 0 and 600
call buffer.write(0) //the item, between 0 and 600
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, "Encoder Version: " + GetCurrentEncoder().toString())
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, buffer.code)
return false
endfunction
The reason not all of the items were written is because of that 0 and the way the links were set up.
The slot where the 0 was written to is item2. The link between item2 and item3 requires a value between 1 and 600. Because 0 isn't in that range, the code stops at item2. Another example using the same Encoder.
JASS:
private function Save takes nothing returns boolean
local player triggerPlayer = GetTriggerPlayer()
local integer playerId = GetPlayerId(triggerPlayer)
local DataBuffer buffer = GetCurrentEncoder().write(playerId)
call buffer.write(12) //the hero, between 1 and 25)
call buffer.write(32) //the item, between 0 and 600
call buffer.write(500) //the item, between 0 and 600
call buffer.write(300) //the item, between 0 and 600
call buffer.write(0) //the item, between 0 and 600
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, "Encoder Version: " + GetCurrentEncoder().toString())
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, buffer.code)
return false
endfunction
In this case, the 0 is written into item4. It is also possible that no 0s are written, meaning that the inventory is full.
JASS:
private function Save takes nothing returns boolean
local player triggerPlayer = GetTriggerPlayer()
local integer playerId = GetPlayerId(triggerPlayer)
local DataBuffer buffer = GetCurrentEncoder().write(playerId)
call buffer.write(12) //the hero, between 1 and 25)
call buffer.write(32) //the item, between 0 and 600
call buffer.write(500) //the item, between 0 and 600
call buffer.write(300) //the item, between 0 and 600
call buffer.write(100) //the item, between 0 and 600
call buffer.write(52) //the item, between 0 and 600
call buffer.write(253) //the item, between 0 and 600
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, "Encoder Version: " + GetCurrentEncoder().toString())
call DisplayTimedTextToPlayer(triggerPlayer, 0, 0, 60, buffer.code)
return false
endfunction
Ids can also be used to see if slots exist. For example, saving item charges-
JASS:
if (buffer.id == ITEM_CHARGE ) then
call buffer.write(GetItemCharges(someItem))
endif
Saving and loading require coding knowledge.
If you noticed, there is only one Save function. The framework will always save using the latest Encoder version. However, if you look, the same concept for Catalogs and Encoders is used for loading, thus allowing one to load from older codes.
In the case of loading, a new Loader must be used for a new Encoder, even if that Loader is identical to the old one.
In the case of Loaders, values are read from the buffer, so buffer.read is used.
For example, reading from a code using this Encoder-
JASS:
//! textmacro ENCODER_1
private function Encoder1 takes nothing returns nothing
local Encoder encoder = BuildEncoder(1) //version 1
local CodeRange gold = CodeRange.create(0, 1000000)
local CodeRange lumber = CodeRange.create(0, 1000000)
call encoder.add(gold)
call encoder.add(lumber)
endfunction
//! endtextmacro
JASS:
//! textmacro LOAD_1
private function Load1 takes nothing returns nothing
local player triggerPlayer = GetTriggerPlayer()
local integer playerId = GetPlayerId(triggerPlayer)
local DataBuffer buffer = bf
call SetPlayerState(triggerPlayer, PLAYER_STATE_RESOURCE_GOLD, buffer.read())
call SetPlayerState(triggerPlayer, PLAYER_STATE_RESOURCE_LUMBER, buffer.read())
endfunction
//! endtextmacro
buffer.id may be used to check if slots exist or not as before-
JASS:
if (buffer.id == ITEM_CHARGE) then
call SetItemCharges(myItem, buffer.read())
endif
Example of loading a hero
local unit u = CreateHero(HeroCatalog1[buffer.read()].raw, triggerPlayer)
Last edited: