I could walk you through it if you like.
Ok, the triggers you'll need to look at are in the demo folder.
The first one is the Catalog.
From line 17 on-
struct Catalog extends array
private static constant integer CUR_VERSION = 0
That refers to your current version. Just change this to values between 0 and 9.
Notice I do
set encoders[0] = encoder
A bit lower.
You are provided with a pretty useful thing called a textmacro.
JASS:
//! runtextmacro CREATE_CATALOG("Hero")
//! runtextmacro CREATE_CATALOG("Item")
//! runtextmacro CREATE_CATALOG("Pet")
Remember that in save/load codes, you have to create a catalog of most everything you want to save. A catalog would be the arrays you store all your heroes in, items in, and etc. This macro creates catalogs for you. It looks easy to create a new catalog no? It is.
This line creates a catalog called Hero, which is used to store the hero unit type ids in the map
//! runtextmacro CREATE_CATALOG("Hero")
It can be named whatever you want, I just named it Hero because that made sense ;P.
A unit id is the Unit Type Id. If you go into the object editor and hit CTRL+D, you will see the raw code unit ids of units.
The catalog macro is run within the Catalog struct (right where I have the 3 macros at).
JASS:
struct Catalog extends array
private static hashtable table = InitHashtable()
private static constant integer CUR_VERSION = 0
readonly static Encoder array encoders
static method operator encoder takes nothing returns Encoder
return encoders[CUR_VERSION]
endmethod
//put them here
//! runtextmacro CREATE_CATALOG("Hero")
//! runtextmacro CREATE_CATALOG("Item")
//! runtextmacro CREATE_CATALOG("Pet")
The next portion is the init version 0 area
private static method initv0 takes nothing returns boolean
There are 10 initialization areas in the demo, meaning you can have up to 10 active encoders. If you add new features to your map, like a new hero, and you want backwards compatibility, that is where the 10 initialization areas come in handy.
The Encoder object is what actually stores the values you can put into your save/load code. Think of it as having slots that you can plug into.
local Encoder encoder = Encoder.create(Base["0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*():<>?+-"], 148194, 2562)
The long string would be the key I'm using (the characters used for the save/load code). Normally, you scramble this up and people don't normally use special characters.
Example-
5cD234EFGHXYUVQRST789irstuvwxyz6NO01PnWZabIJKLMjklopmqdABCefgh
Encoder.create(Base["5cD234EFGHXYUVQRST789irstuvwxyz6NO01PnWZabIJKLMjklopmqdABCefgh"], 148194, 2562)
The next value is used to make it so codes can't be easily modified. The larger the value, the less likely a code can be modified. Large values will increase the size of the code. I normally use a 6 digit number like what I used here: 148194.
The next value controls how likely one player can load up the code of another player. It's already unlikely, even if this value was 0 or 1 because the code scrambling is unique to every player. However, there is still a very tiny chance that 2 players could load up the same code (though the code would give different data for both of them). This value makes that chance very small. I always make it 2 digits less than the modification protection value so that it doesn't increase the size of the code at all: 2562
The next portion is where things are added to the catalog. The things created are based on what you named the catalog. For example
call addHero('Hpal')
That is only addHero because I named the catalog Hero. If I had named it Pooky, it would have been addPooky. You can pass in any integer value. I'm passing in unit type ids (remember Object Editor, units have ids). Hpal refers to the Paladin. Look it up in Object Editor =P.
I add in tons of heroes...
Next, I move on to the Pet catalog, which takes more units. I'm just adding creeps here actually (neutral hostile i think?).
After that is the Item catalog, where I am going through many of the items in the game and adding them (I went through a lot o-o).
Finally, when you are finished building your catalog, you need to build your encoder. Remember that an encoder has slots that you can plug values into?
Each slot in the Encoder can store a specific range of values, and you can tell it precisely what range you want.
call encoder.add(0,1000000) //gold
That's the very first range I added to the encoder, which means that the first slot in the Encoder can take a value anywhere from 0 to 1000000. Remember a player could have anywhere from 0 to 1000000 gold.
Always add your biggest value first*** to make your code smaller.
Next, I add in the lumber
call encoder.add(0,1000000) //lumber
A player can have between 0 and 1000000 lumber right? Right.
After that, I add in the hero
call encoder.add(1,HeroCount) //hero
Notice I used a variable called HeroCount. Remember the Hero catalog? HeroCount was generated by it. If I had named the catalog Pooky, it would have been PookyCount. It refers to the maximum possible hero you can have. So if you had 10 heroes in your map, it would be 10 and the range would be 1-10. I make the minimum 1 here because a player will always have a hero. 0 would mean that the player might not have a hero, which doesn't make sense.
Next I added in the x
call encoder.add(0,100) //hero x
Remember, I did freaky stuff to make the coordinates a percent. 0 to 100 refers to 0% to 100%. 0 would be at the very bottom of the map and 100 would be at the very top.
Next, the level
call encoder.add(1,10) //hero level
In this map, the minimum level a hero can have is 1 and the max is 10. Can change it, but I don't recommend changing the min unless your heroes can be level 0, or the very first level in the map is something like level 23.
Next, I add the % xp
call encoder.add(0,99) //hero % xp
Notice that this is not 0-100. The reason is because at 100%, the hero has leveled. At max level, the xp would actually be 0%.
Then I have stats
JASS:
call encoder.add(1,256) //str
call encoder.add(1,256) //agi
call encoder.add(1,256) //int
This means that the max stat my heroes can have are 256, but you can change it to whatever. It changes from map to map.
Next, hero life and mana
JASS:
call encoder.add(0,100) //hero life
call encoder.add(0,100) //hero mana
Again, percents.
Next, the hero inventory, which has 6 items in it
call encoder.add(1,ItemCount) //item 1
So this would be able to handle any item. However, this should have been 0 to ItemCount because the hero may not have an item. This means that every single slot has to have an item :| (my mistake)
Next, the item charge
call encoder.add(0,25) //item 1 charge
Notice that this'll make it so that you always have to save an item's charge and you can only save a max charge of 25. Most items in a map actually have a charge of 0. There is a reason charged items typically aren't saved in ORPGs.
On another note, Encoder 2.0.0.0 will be able to handle charged items brilliantly. If you save an item and it has a max charge of 100, it'll make a slot to store that charge =P. If it has a max charge of 0, it won't make a slot. Nice eh? : )
And yea, doing the other 5 items in the inventory
JASS:
call encoder.add(1,ItemCount) //item 2
call encoder.add(0,25) //item 2 charge
call encoder.add(1,ItemCount) //item 3
call encoder.add(0,25) //item 3 charge
call encoder.add(1,ItemCount) //item 4
call encoder.add(0,25) //item 4 charge
call encoder.add(1,ItemCount) //item 5
call encoder.add(0,25) //item 5 charge
call encoder.add(1,ItemCount) //item 6
call encoder.add(0,25) //item 6 charge
Then I made it so that a hero could have 4 pets in this map, so I store them all.
call encoder.add(1,PetCount) //pet1
Notice my mistake. The minimum is 1, meaning the hero always has to have a pet :\. It should have been 0 to PetCount.
And store all of the pet's stats
JASS:
call encoder.add(0,100) //pet1 x
call encoder.add(0,100) //pet1 y
call encoder.add(0,100) //pet1 facing
call encoder.add(0,100) //pet1 life
call encoder.add(0,100) //pet1 mana
Yawn, you should be getting it at this point
And the rest of the pets
JASS:
call encoder.add(1,PetCount) //pet2
call encoder.add(0,100) //pet2 x
call encoder.add(0,100) //pet2 y
call encoder.add(0,100) //pet2 facing
call encoder.add(0,100) //pet2 life
call encoder.add(0,100) //pet2 mana
call encoder.add(1,PetCount) //pet3
call encoder.add(0,100) //pet3 x
call encoder.add(0,100) //pet3 y
call encoder.add(0,100) //pet3 facing
call encoder.add(0,100) //pet3 life
call encoder.add(0,100) //pet3 mana
call encoder.add(1,PetCount) //pet4
call encoder.add(0,100) //pet4 x
call encoder.add(0,100) //pet4 y
call encoder.add(0,100) //pet4 facing
call encoder.add(0,100) //pet4 life
call encoder.add(0,100) //pet4 mana
And that's that. You can also store abilities, but that requires you make an ability catalog or a special ability catalog unique to each hero. You'd do it just the same way, add each ability to the catalog, then add slots to the encoder to store the ability and its level.
Ex
JASS:
call encoder.add(0,AbilityCount)
call encoder.add(0,3) //ability level
If the ability was level 0, then they may have it, but it's not been learned yet ;P. However, if when they first get the ability it's at level 1, then you could make it 1,3.
JASS:
call encoder.add(0,AbilityCount)
call encoder.add(1,3) //ability level
And if they could have like 12 abilities, you'd have to add it 12 times.
So now the encoder is built as well as the catalogs.
The next trigger to look at is Saving
Scroll down to this area
JASS:
set encoder = Catalog.encoder
//first open the encoder for the triggering player
//because this is saving, don't pass it a code
call encoder.open(null, pid)
First, I read the latest encoder from Catalog.encoder, just so I know what to use for my encoder. Next, I open the encoder. pid refers to the player's id (I make a little variable at the top of the function) and null refers to the code. If the code is null, it'll open up for writing. If the code is not null, it'll open for reading.
And here are my locals just so that it is clear where these variables are coming from
JASS:
local player p = GetTriggerPlayer()
local integer pid = GetPlayerId(p)
local Encoder encoder
local integer i
local integer i2
local unit u
local item it
Just scroll down to
call DataBuffer.write(GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD))
Remember that the first slot added to the encoder was the gold? Just read the player's gold and write it to the buffer. You can easily do this in GUI as well
-
Actions
-
Set number = ((Triggering player) Current gold)
-
Custom script: call DataBuffer.write(udg_number)
GetPlayerState would be the JASS native that retrieves a player's properties, like gold.
So I am just writing data into the buffer in the order that I added to the encoder.
The order was
JASS:
/*
gold, lumber
hero
%x, %y, %facing, level, %xp
hero str, agi, int
hero %life, hero %mana
item1, charge
item2, charge
item3, charge
item4, charge
item5, charge
item6, charge
pet1
x%, y%, facing%, life%, mana%
pet2
x%, y%, facing%, life%, mana%
pet3
x%, y%, facing%, life%, mana%
pet4
x%, y%, facing%, life%, mana%
*/
Sure enough, next is lumber
call DataBuffer.write(GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER))
Just read the lumber and wrote it into buffer. Again, can easily be done in GUI.
Next the hero
call DataBuffer.write(Catalog.getHeroId(GetUnitTypeId(u)))
Notice I used Catalog.getHeroId. When creating a catalog, it also creates a getId type thingie so that you can convert a unit id into a catalog id, thus making the number way smaller. The thing takes the unit id.
GetUnitTypeId(u)
would return like Hpal for a Paladin.
So together,
Catalog.getHeroId(GetUnitTypeId(u))
Where I am passing the unit type id of the unit into the catalog so that I can convert it into a hero catalog id. I then pass that hero catalog id into the DataBuffer.
If I had named the catalog Pooky, it would be Catalog.getPookyId and I would pass in a well... a pooky I guess, lol... for pets, remember that they are units, so I still pass in GetUnitTypeId.
Next are the coords
JASS:
call DataBuffer.write(GetPercentUnitX(u))
call DataBuffer.write(GetPercentUnitY(u))
GetPercentUnitX will retrieve a unit's percent position on the map given the unit. It is part of the UnitStatePercent library.
Then facing, level, and xp. Notice that the facing and xp are again retrieved as percents. The hero level is not.
JASS:
call DataBuffer.write(GetPercentUnitFacing(u))
call DataBuffer.write(GetHeroLevel(u))
call DataBuffer.write(GetPercentHeroXP(u))
And all of the stats. The false means to not include bonuses in with the stats (bonuses from things like armor).
JASS:
call DataBuffer.write(GetHeroStr(u, false))
call DataBuffer.write(GetHeroAgi(u, false))
call DataBuffer.write(GetHeroInt(u, false))
And percent life and mana
JASS:
call DataBuffer.write(GetPercentUnitLife(u))
call DataBuffer.write(GetPercentUnitMana(u))
The inventory is a little trickier as you need to loop over the items in a unit's inventory.
JASS:
set i = UnitInventorySize(u)
set i2 = 6-i
loop
set i = i - 1
set it = UnitItemInSlot(u, i)
call DataBuffer.write(Catalog.getItemId(GetItemTypeId(it)))
call DataBuffer.write(GetItemCharges(it))
exitwhen i == 0
endloop
loop
exitwhen i2 == 0
call DataBuffer.write(0)
call DataBuffer.write(0)
set i2 = i2 - 1
endloop
This essentially loops through each item in the unit's inventory and writes it in. However, what if the unit only had an inventory size of 4? Remember there are 6 spaces for items in the code, so 6 items have to be written, even if the rest are 0s. The second loop writes in 0s. If the unit had an inventory size of 4, it'd write in 2 0s at the end. Notice that I first write the item, then I write its charges. Remember that when I built the encoder, I first added a slot for the item, then the charge.
Next, I read each pet
JASS:
call DataBuffer.write(Catalog.getPetId(GetUnitTypeId(u)))
call DataBuffer.write(GetPercentUnitX(u))
call DataBuffer.write(GetPercentUnitY(u))
call DataBuffer.write(GetPercentUnitFacing(u))
call DataBuffer.write(GetPercentUnitLife(u))
call DataBuffer.write(GetPercentUnitMana(u))
And yea, it gets boring. Essentially, all you need to know is to write data to the encoder, you use DataBuffer.write =P, and you have to write in the exact same order that you built the encoder in.
Finally, I display the encoder id and the code
JASS:
call DisplayTimedTextToPlayer(p, 0, 0, 60, "Encoder Version: " + encoder.toString())
call DisplayTimedTextToPlayer(p, 0, 0, 60, DataBuffer.code)
DataBuffer.code returns the code, colorized and ready to go. That's all that needs to be done. encoder.toString() (where encoder is your Encoder object) returns the encoder as a little mini code. This allows a player to do something like -load encoder code for older codes.
Loading is done the exact same way except that you use Read first...
All of this garbage is for loading older codes
JASS:
set s = SubString(s, 6, StringLength(s))
loop
set char = SubString(s, i, i+1)
exitwhen char == DELIMITER or i == k
set i = i + 1
endloop
if (char == DELIMITER) then
set s3 = SubString(s, 0, i)
set s2 = ""
set k = StringLength(s3)
loop
set k = k - 1
set char = SubString(s3, k, k+1)
if (char != " ") then
set s2 = char + s2
endif
exitwhen k == 0
endloop
set encoder = Encoder.convertString(s2)
debug if (encoder > 0) then
set s = SubString(s, i+1, k)
debug else
return false
debug endif
else
set encoder = Catalog.encoder
endif
Don't worry yourself over it ;P.
The portion you want to look at is
if (encoder.open(s, pid)) then
That will attempt to open the code with the encoder. If it returns true, the load was successful. It if returns false, the load wasn't successful. Again, pid refers to the player id of the player. s refers to the code.
You have to rip the little -load portion out of the s, which is a portion of what my jumbled mess of code above does.
So, just read...
call SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, DataBuffer.read())
Sets the player's gold to what's read.
call SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER, DataBuffer.read())
Sets the player's lumber
CreateUnit(p, Catalog.Hero[DataBuffer.read()], PercentToX(DataBuffer.read()), PercentToY(DataBuffer.read()), PercentToFacing(DataBuffer.read()))
That does a whole bunch of reading. First, it reads the hero. Catalog.Hero will convert a hero id into a unit type id.
Catalog.Hero[DataBuffer.read()]
Then it reads the percent x. PercentToX will convert the percent x into a valid x coordinate.
PercentToX(DataBuffer.read())
Then the percent y and percent facing. It uses all of this to create a unit.
Next, it reads the level
set lvl = DataBuffer.read()
I stored it into a variable because if you try to set the level of a level 1 unit to 1, it'll set it to 2... it's a funky bug.
JASS:
if (lvl > 1) then
call SetHeroLevel(u, lvl, false)
endif
That essentially says to only set the hero level if the level is bigger than 1 ;P. Bug avoided ;D.
Then I add the percent hero xp with the special function
call AddPercentHeroXP(u, DataBuffer.read())
Notice I'm just reading everything in the same order that I wrote.
And here comes the stats
JASS:
call SetHeroStr(u, DataBuffer.read(), true)
call SetHeroAgi(u, DataBuffer.read(), true)
call SetHeroInt(u, DataBuffer.read(), true)
The true makes them permanent.
And then I read life and mana
JASS:
set lp = DataBuffer.read()
set mp = DataBuffer.read()
Remember, I read life and mana while the hero gear was on, so first I need to add the gear to the hero and then set the life and mana. That's why I just didn't set it out right.
Add all the gear
JASS:
set i = UnitInventorySize(u)
set i2 = 6-i
loop
set i = i - 1
call UnitAddItemToSlotById(u, Catalog.Item[DataBuffer.read()], i)
call SetItemCharges(UnitItemInSlot(u, i), DataBuffer.read())
exitwhen i == 0
endloop
loop
exitwhen i2 == 0
call DataBuffer.read()
call DataBuffer.read()
set i2 = i2 - 1
endloop
Same deal with the 0s. If the hero had an inventory of 4, then I just read out a few 0s.
Now I set life and mana with my awesome percent functions
JASS:
call SetPercentUnitLife(u, lp)
call SetPercentUnitMana(u, mp)
Then the first pet
JASS:
set u = CreateUnit(p, Catalog.Pet[DataBuffer.read()], PercentToX(DataBuffer.read()), PercentToY(DataBuffer.read()), PercentToFacing(DataBuffer.read()))
set pet1[pid] = u
call SetPercentUnitLife(u, DataBuffer.read())
call SetPercentUnitMana(u, DataBuffer.read())
And the other 3 pets ;|
JASS:
set u = CreateUnit(p, Catalog.Pet[DataBuffer.read()], PercentToX(DataBuffer.read()), PercentToY(DataBuffer.read()), PercentToFacing(DataBuffer.read()))
set pet2[pid] = u
call SetPercentUnitLife(u, DataBuffer.read())
call SetPercentUnitMana(u, DataBuffer.read())
set u = CreateUnit(p, Catalog.Pet[DataBuffer.read()], PercentToX(DataBuffer.read()), PercentToY(DataBuffer.read()), PercentToFacing(DataBuffer.read()))
set pet3[pid] = u
call SetPercentUnitLife(u, DataBuffer.read())
call SetPercentUnitMana(u, DataBuffer.read())
set u = CreateUnit(p, Catalog.Pet[DataBuffer.read()], PercentToX(DataBuffer.read()), PercentToY(DataBuffer.read()), PercentToFacing(DataBuffer.read()))
set pet4[pid] = u
call SetPercentUnitLife(u, DataBuffer.read())
call SetPercentUnitMana(u, DataBuffer.read())
Hopefully you now know how to use Encoder =P. This was just how I decided to do it in the demo map, but you can do w/e you like for catalogs and you don't necessarily have to load up all of this data. You can build whatever encoder you like ^)^.
As I said, the code is actually pretty simple and you could probably handle it without any problems, it was just my implementation that turned kind of complicated =P.
And again, the save/load code in the map packs all of this
JASS:
/*
Gold: 688620
Lumber: 888649
Bronze Drake
upper right
facing out
Black Drake
upper left
facing in
Gnoll
Bottom left (close)
facing out
Furbolg Elder Shaman
Right
facing out
Far Seer
Center
Facing up
Level: 7
xp: 2742
Strength: 27
Agility: 24
Intelligence: 37
Scepter of Mastery
3/3
Celestial Orb of Souls
0/0
Orb of Venom
0/0
Greater Scroll of Replenishment
1/1
Serathil
0/0
Shimmerglaze Roast
5/6
Mana: 365
Hp: 665
Name saving code:
WorldEdit
Code:
10%O (ijD k@Oa ?@<f Oe4a &sha J%AP wQlV 0pHZ tbF1 (vE0 n&ZQ Rk3^ tA4$ eD
*/
Notice how small the code is for that insane amount of data. In pretty much any other save/load code, you are probably looking at 110+ characters. That code I have there is 58 characters long. That is a LOT shorter than 110 no? : P