Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
Hey guys, I need a save/load system that saves your hero's level, stats, abilities(so when you -load, you won't have to learn your abilities again through the red cross), Gold, Lumber, Level of the abilities, so if i had a Level 5 storm bolt, and when i load, it'll make it so i'll already learned the level 5 storm bolt for me, and i won't have to learn it through the red cross.
Yeah, you can, as long as you save it. The only issue is that you need to keep track of what abilities each unit has, as there isn't a way to directly retrieve what abilities a unit has.
My hero is able to buy a item, that will triggerly learn him an ability, and when you buy more of that item it'll level that ability up, when i -save, will it load that abilites and other abilities that hero has learned through vendors?
//iGen does it in 17 w/ security (prob 15-16 w/o)
//CodeGen does it in 16 w/o security
//Acehart's does it in 16
//PipeDream's Optimal Save System does it in 7 chars w/ 0 security
//Encoder does it in 7 chars w/ 0 security
You can see the results for yourself.
My hero is able to buy a item, that will triggerly learn him an ability, and when you buy more of that item it'll level that ability up, when i -save, will it load that abilites and other abilities that hero has learned through vendors?
Also, Encoder 2.0.0.0 will be able to save things like item charges =), but it's taking me a very long time to code it o-o.
So the only save/load system I really recommend you use is Encoder as that's more customizable than PipeDream's, supports multiple save/load code versions, and has a chance to output smaller codes w/ security as well as uses ranges rather than maxes and can encode negative values.
It is probably the best save/load system to date, and 2.0.0.0 will be superior to every other save/load system ever created for wc3, the others won't even be able to come close to it.
You will need a sort of table system (using arrays and hashtables) to map unit types to a list of abilities they can have. That way you only need to store the level of abilities and not which abilities (saves space).
//iGen does it in 17 w/ security (prob 15-16 w/o)
//CodeGen does it in 16 w/o security
//Acehart's does it in 16
//PipeDream's Optimal Save System does it in 7 chars w/ 0 security
//Encoder does it in 7 chars w/ 0 security
You can see the results for yourself.
If you save them into the code, yes.
Also, Encoder 2.0.0.0 will be able to save things like item charges =), but it's taking me a very long time to code it o-o.
So the only save/load system I really recommend you use is Encoder as that's more customizable than PipeDream's, supports multiple save/load code versions, and has a chance to output smaller codes w/ security as well as uses ranges rather than maxes and can encode negative values.
It is probably the best save/load system to date, and 2.0.0.0 will be superior to every other save/load system ever created for wc3, the others won't even be able to come close to it.
Minimal vjass knowledge is required. It's pretty easy.
For custom script, make a global called encoder and then do like set encoder = Encoder.create("[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"]) or w/e.
Then do like Encoder(encoder).add(lowBound, highBound), but this would probably be best done in plain text.
Just check out the demo map and use that as a template, most of it is variable sets and I'm sure you can read that ; P. For the actual saving portion, you can just do GUI and keep in mind DataBuffer.write() and DataBuffer.read() = ).
But really, is it not worth it for a code of half the size? : |
Yeah, the code is super long, if i could minimize the code somehow, and yeah i've looked at the demo map, looks really complex for a -save and -load system since it saves the percentage of the exp bar and the start location of where you saved your character onto that map, i'd really love to use this system but the only problem is that i don't know where to start since its in jass and i don't know a thing about jass.
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).
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.
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%.
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? : )
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.
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.
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.
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.
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.
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.
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())
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
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
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.