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!
Salutations, I am in dire need of a save/load feature if Wrath of the Kaiser is to continue.
I wish I could word this better, but I don't exactly know how to ask for help with this.
Would any of you happen to know how to set up a system rather like that of The Black Road wherein one types "-save" and that results in a code popping up which contains the inventory, character, and gold?
The thing is you just copy the code, you don't need to do anything.
At least that's the concept of the whole thing.
edit: There is a GUI demo.
Warning
Synchronization of data in multiplayer games can take a few minutes in-game to sync. The longer the code, the longer it takes to sync.
MD5 adds 128 bits to the code
AES formats the code to 128 bit blocks
Using either of these will make the sync time really long (2-5 minutes). Players will not be able to do anything at all until the synchronization is complete. Units won't be able to be ordered, etc. Cinematics also won't be able to be done as these break Network.
At this point, unless you have a tiny amount of data (like 1-3 numbers), I recommend traditional save/load. Furthermore, I do not recommend enabling any of the protection unless your game can only have 2-3 players in it. I recommend using Scrambler and Knuth Checksum otherwise. Knuth minimally increases code size and Scrambler just scrambles up the code, no formatting required. As such, you'll have to use BigInt and then put all of the digits (32 bit or 16 bit or w/e) into BitInt. Right now BigInt doesn't allow you to do this easily.
Another warning is that this does not currently support selecting a user hard drive. As such, if the user has no C drive, the user will disconnect.
Codeless Save/Load
v1.0.1.1
by Nestharus
__
[td]
The map provided supports both vJASS and GUI
It can be used to implement codeless save/load in Warcraft 3, similar to Starcraft 2 banks
Codeless save/load will only work on Windows machines. There are very few mac players, so this isn't a big deal
/*
* This map includes
*
* 1. save/load system
* - basic save/load
* - does not actual implement save/load into a map, this is used
* - to do save/load***
*
* 2. encryption system (extra)
* - AES 128 bit
*
* Encryption = scrambling
*
* 3. cryptographic hash system (extra)
* - MD5
*
* Hash = tamper protection
*
* The 3 above systems can be used to create a map specific save/load system,
* either for vJASS or GUI.
*
* The included GUI Save/Load System is one example of such a system, created
* for GUI users and general maps. It does not support advanced features as
* you'll later learn about though.
*
* Besides the 3 systems, a collection of snippets are also included for
* saving units
*
* SaveLoad Unit (see trigger comment for API)
*/
/*
* The save/load core is where to place information general to your save/load
* system. This is purely for organizational purposes.
*
* The core may include
*
* 1. map name
* 2. encoder array
* 2a. encryption strength (for encoders)
* 2b. encoder password (for encoders)
* 3. apply hash boolean
*/
/*
* Example
*/
scope SaveLoadCore
/*
* Settings
*/
globals
/*
* This is the name of the map
*/
constant string MAP_NAME = "MyFirstMap"
/*
* A more advanced save/load system may be used when saving perhaps
* multiple heroes for a given map.
*
* The naive approach to save multiple heroes is saving them all in
* one code, then loading the entire code and allowing the player
* to pick which hero they want to play as.
*
* The smart approach is making 1 code for each hero and then having
* a separate code that contains the list of heroes. This will make
* the save/load faster.
*
* Of course, alternatively, you could allow a player to pick a hero
* and then attempt to load that hero before generating a new hero
* (1 code per hero without the list).
*
* If you'd prefer players be able to create multiple of the same
* hero, you can have a list for each hero.
*
* Putting all of the heroes into one code does improve security,
* but it comes at the hefty cost of speed (possible FPS drop)
* and data limits (limit is ~65,000 bits of data per code).
*
* As such, the demonstration of save/load with vJASS provided
* within this map is only a demonstration of what can be done.
* It is not meant to be used as a system. This demonstration only
* supports the naive approach stated above.
*/
/*
* Encryption settings
*/
constant integer ENCRYPTION_STRENGTH = 2 //1 to 16
constant string ENCRYPTION_PASSWORD = "Hohoho"
/*
* Hash settings
*/
constant boolean APPLY_HASH = true
endglobals
globals
Encoder array encoder //these are used for encryption and decryption
endglobals
private struct CoreInit extends array
/*
* This method must be executed as encoders use synchronous natives
*/
private static method init takes nothing returns nothing
local integer playerId
/*
* Encoder creation
*/
if (ENCRYPTION_STRENGTH > 0) then
set playerId = 11
loop
if (/*
*/GetPlayerSlotState(Player(playerId)) == PLAYER_SLOT_STATE_PLAYING and /*
*/GetPlayerController(Player(playerId)) == MAP_CONTROL_USER /*
*/) then
/*
* The player name is used with the encryption password to make
* encryption both player unique and map unique
*
* Encoder creation generates a cipher by applying MD5 to the
* player name + password
*/
set encoder[playerId] = Encoder.create(GetPlayerName(Player(playerId)) + ENCRYPTION_PASSWORD)
endif
exitwhen 0 == playerId
set playerId = playerId - 1
endloop
endif
endmethod
private static method onInit takes nothing returns nothing
call init.execute()
endmethod
endstruct
endscope
/*
* Saving may be accomplished with one of two techniques.
*
* Technique #1
*
* A -save command may be used just like in traditional save/load. When
* the player types -save, their information is saved.
*
* Technique #2
*
* A timer can be used to save a player's information whenever it
* expires. This will mean that the player will not have to save
* information themselves and can leave the game freely. This is great
* if a player disconnects as they won't loes their information.
*
* The danger in using a timer is that the player might leave the game
* while their code is being written to the file containing their
* information. When using periodic saving, you should alternate between
* two files so that if one file becomes corrupt due to a player leaving,
* the other file will still be ok. This problem can also occur with
* technique #1 when a player disconnects while their code is being written
* to a file.
*
* The chances of a corrupt file are not at all rare. Save/Load is not
* instantaneous and actually occurs over a period of time.
*
* The GUI version of save/load does not support alternating between two
* files for ultimate player safety. Nor will this vJASS demonstration.
* As such, someone may want to write up a GUI Save/Load system using
* the 3 systems of this map that does have file alternation.
*
* For file alternation, a third file must be used to determine the last
* correctly saved file.
*/
scope Save
private struct Save extends array
private static method save takes integer playerId, SaveCode data returns nothing
/*
* SaveCode essentially has 1 method for writing data
*
* call data.write(integer)
*
* So writing data to it is very simple. Any integer can be
* written, negative or positive.
*/
call data.write(15)
call data.write(-24298)
call data.write(38395)
endmethod
private static method saveComplete takes integer playerId returns nothing
call DisplayTimedTextToPlayer(Player(playerId),0,0,60000,"Save Complete")
endmethod
/*
* Remember that a save is not instant. This will ensure that
* a player does not save while a save is already in progress.
*/
private static boolean array isSaving
/*
* Notice that this is not a trigger condition, but rather a
* trigger action. This is because synchronus natives will be
* used.
*/
private static method onSave takes nothing returns nothing
local integer playerId
local SaveCode data = 0
local Thread thread
set playerId = GetPlayerId(GetTriggerPlayer())
if (isSaving[playerId]) then
return
endif
set isSaving[playerId] = true
/*
* Threads are used for synchronization
* In this case, the Thread is used to make the non saving players
* wait until the saving player is finished saving
*
* A thread will not be considered synchronized until all players in the game
* have sent synchronization requests. Every player except for the saving
* player will send a sync request with the following code.
*/
set thread = Thread.create()
if (GetPlayerId(GetLocalPlayer()) != playerId) then
call thread.sync()
endif
/*
* This loop will continue until the player is finished saving
* The loop exits when the thread is synchronized, and the saving player
* does not sync the thread until that player has finished saving.
*/
loop
if (GetPlayerId(GetLocalPlayer()) == playerId) then
/*
* A save code is only created for the saving player
*/
set data = SaveCode.create()
/*
* create a save method outside of this area for cleaner code :)
*/
call save(playerId, data)
/*
* The player id is set to -1 here so that the player does not enter
* this area again
*/
set playerId = -1
call thread.sync()
endif
/*
* TriggerSyncReady is used here to prevent op limit + for short wait
* It is a synchronous native
*/
call TriggerSyncReady()
exitwhen thread.synced
endloop
set playerId = GetPlayerId(GetTriggerPlayer())
/*
* FormatBitInt is a major operation, thus an evaluation is used
* It formats the save/load code to be in a multiple of 128 bits
* This is necessary because the LoadFile command reads out 128
* bits at a time. As such, it may end up reading out extra 0s, which
* will end up breaking the decryption process.
*/
call FormatBitInt.evaluate(data)
/*
* The hash is applied to the start of the code. Apply the hash before
* encryption so that players will not have access to the hash.
*
* The encryption goes from left to right (start to back). This means that
* the hash, if the encryption strength is >= 2, will affect the rest of the
* code. The hash is dependent on the rest of the code, so having a hash and
* encryption strenght >= 2 will end up making the entire code look
* completely different with tiny bit changes.
*
* encryption and hashing have synchronous native calls in them, so *ALL*
* players must call them or a desync will occur.
*/
if (APPLY_HASH) then
call ApplyHash(data)
endif
call encoder[playerId].encrypt(data, ENCRYPTION_STRENGTH)
/*
* The second argument is the file name. For this, can just use the
* playe's name. The file will only save for the saving player because
* data == 0 for all of the other players.
*
* SaveFile has synchronous native calls in it, so *ALL* players
* must call it or a desync will occur.
*/
call SaveFile(MAP_NAME, GetPlayerName(GetLocalPlayer()), data)
/*
* Destroy the save code
*/
if (0 != data) then
call data.destroy()
endif
/*
* Allow the saving player to save again
*/
set isSaving[playerId] = false
/*
* You may want to let the player know that the save was completed
* This can be done with a multiboard, text tags, or even a game message
*
* Remember that a code can become corrupt if a player leaves while they are
* still saving, so letting them know when their save is complete is a must.
* This is only true for the -save command. For periodic saving, it is not
* important to let a player know when their save is complete.
*/
call saveComplete(playerId)
endmethod
private static method onInit takes nothing returns nothing
local integer playerId = 11
local trigger t = CreateTrigger()
call TriggerAddAction(t, function thistype.onSave)
/*
* Registration of player -save command
*/
loop
if (GetPlayerSlotState(Player(playerId)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(playerId)) == MAP_CONTROL_USER) then
call TriggerRegisterPlayerChatEvent(t, Player(playerId), "-save", true)
endif
exitwhen 0 == playerId
set playerId = playerId - 1
endloop
set t = null
endmethod
endstruct
endscope
/*
* Loading may either be done when the map starts (load up all data for
* a player) or done in 2 stages.
*
* Stage 1
*
* Load up the file containing the list of heroes
*
* Or
*
* Wait until the player picks a hero
*
* Stage 2
*
* Load hero that player picked
*
* For this demonstration, it will simply load up all data for a player.
*
* If alternating files are used, any given hero will have three files
*
* File 1: last *fully* saved file (used to pick code #1 or code #2)
* File 2: code #1
* File 3: code #2
*
* For this demonstration, only 1 file will be used.
*/
scope Load
private struct Load extends array
private static method load takes LoadCode data returns nothing
/*
* LoadCode contains 2 useful things
*
* data.playerId
* - playerId refers to the loading player
*
* data.read()
* - reads integer out of code in order that they were written
* - in
*/
/*
* 15
*/
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Loaded: "+I2S(data.read()))
/*
* -24298
*/
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Loaded: "+I2S(data.read()))
/*
* 38395
*/
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Loaded: "+I2S(data.read()))
/*
* 0 (no data left)
*
* BitInt(data).bitCount may or not be 0 though
*/
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Loaded: "+I2S(data.read()))
endmethod
/*
* This method can be used to show the download progress for all players
*
* Use multiboard, text tag, or game message
*/
private static method updateProgress takes nothing returns nothing
local integer playerId = 11
call ClearTextMessages()
loop
/*
* If the player is not human, the load progress is -1, so only
* display human load progress.
*
* This can be useful for a progress multiboard as players with
* -1 can be shown in red
*
* Players < 100 can be shown in white
* Players == 100 can be shown in green
*/
if (GetLoadProgress(playerId) > -1) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Download Progress For (" + GetPlayerName(Player(playerId)) + "): "+R2S(GetLoadProgress(playerId)) + "%")
endif
exitwhen 0 == playerId
set playerId = playerId - 1
endloop
endmethod
/*
* This method is executed as it has synchronous calls
*/
private static method onLoad takes nothing returns nothing
local LoadStream stream
local LoadCode data
local timer t
local boolean validated
/*
* Load the file
*
* A timer is used to display download progress
*/
set t = CreateTimer()
call TimerStart(t,.03125000,true,function thistype.updateProgress)
/*
* Notice that LoadFile returns a LoadStream. A LoadStream essentially
* contains all of the codes for all of the players for the given file.
*/
set stream = LoadFile(MAP_NAME, GetPlayerName(GetLocalPlayer()))
call PauseTimer(t)
call DestroyTimer(t)
set t = null
/*
* If File.enabled is false, then the player is currently not able
* to load files.
*
* Let them know somehow how to enable loading.
*
* Step 1. Go to C Drive
* Step 2. Go to !! AllowLocalFiles folder
* Step 3. Run AllowLocalFiles.bat
* Step 4. Restart Warcraft 3
*
* Even if a player doesn't have loading enabled, they will still have
* saving enabled, so they do not have to restart Warcraft 3 until
* they have finished their current game. Just let them know to restart
* before joining another game.
*
* A player will only ever have to run AllowLocalFiles.bat once per
* machine.
*
* I let them know via game messages here, but you might want to let
* them know via a multiboard or quest
*/
if (not File.enabled) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Loading is currently disabled for you")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"To enable loading")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000," ")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"1. Go to C Drive")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"2. Go to !! AllowLocalFiles folder")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"3. Run AllowLocalFiles.bat")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"4. Restart Warcraft 3")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"You do not have to restart Warcraft 3 until after this game. Saving is Enabled.")
endif
/*
* There is no need for a Thread here because all of the players
* have the same data
*/
loop
/*
* Get the next code from the stream
*
* If the code was 0, then there are no codes left in the stream
*
* data.playerId gives the player that owns the code
*/
set data = stream.read()
exitwhen 0 == data
/*
* First decrypt the code and then remove the hash if hashing
* is used
*/
call encoder[data.playerId].decrypt(data, ENCRYPTION_STRENGTH)
set validated = not APPLY_HASH or ValidateHash(data)
if (validated) then
/*
* If the code was valid, load it
*/
call load(data)
else
/*
* Load Failed, code is corrupt
*/
endif
call data.destroy()
call TriggerSyncReady()
endloop
call stream.destroy()
endmethod
/*
* Initialization is done in 3 stages
*
* Stage 1
* run a timer
*
* Stage 2
* execute
*
* Stage 3
* wait for the game to start (synchronous)
*
* begin loading (loading must be done AFTER all players
* have finished loading the map)
*/
private static method init2 takes nothing returns nothing
call WaitForGameToStart()
call onLoad()
endmethod
private static method init takes nothing returns nothing
call DestroyTimer(GetExpiredTimer())
call init2.execute()
endmethod
private static method onInit takes nothing returns nothing
call TimerStart(CreateTimer(),0,false,function thistype.init)
endmethod
endstruct
endscope
/*
* So far, only single version save/load has been discussed. Let's consider
* a map that starts with this information
*
* hero
* xp
* strength
* agility
* intelligence
* life
* mana
* position (x, y, facing)
* inventory
* item 1
* charges
* item 2
* charges
* item 3
* charges
* item 4
* charges
* item 5
* charges
* item 6
* charges
*
* Later, in version 2 of the map, the author decides to add a pet. They
* also decide to add a bank that can hold gear.
*
* pet
* xp
* life
* mana
* position (x, y, facing)
*
* bank
* item 1
* charges
* item 2
* charges
* item 3
* charges
* item 4
* charges
* item 5
* charges
* item 6
* charges
*
* With single version save/load, this is impossible
*
* Muti-Version save/load means to adopt this architecture
*
* Saver
*
* Loader Version 1
* Loader Version 2
* Loader Version 3
* Loader Version X (4+ etc)
*
* Whenever the architecture of the code is changed, like new information
* is added, a new loader is added
*
* The version of the code can be stored at the start of the code
*
* call code.write(version)
*
* Later, the version is read out of the code
*
* version = code.read()
*
* And the correct loader is executed
*
* ExecuteFunc("Loader" + I2S(version)) //functions with
* //loader # on them
*
* or
*
* call TriggerExecute(loaders[version]) //trigger array
*
* This will allow a map to load up both old codes from previous map
* versions and new codes.
*
* Only 1 saver is needed because the map should always save using the
* latest version. There is no purpose to saving codes with old map versions.
*/
/*
* To improve the speed of save/load and reduce size(allowing more data),
* traditional save/load compression techniques can still be utilized
*
* Catalogs
* Lossy Compression
* Partial Sets
* Range shifting (when the max data is 31 bits)
*
* See save/load with snippets for compression techniques
* and resources
*
* hiveworkshop.com/forums/spells-569/save-load-snippets-v2-1-0-5-a-202714/?prev=mmr%3D6
*
* When using these techniques, do not use SaveCode or LoadCode,
* use BitInt directly
*
* data = BitInt.create()
* data.write(value, bitSize)
* data.read(bitSize)
* bitSize = GetBitSize(max size)
*/
/*
function ApplyHash takes BitInt data returns nothing
- apply hash to code
hash makes it very difficult for a player to modify a code
function ValidateHash takes BitInt data returns boolean
- validates a code (given that a hash was applied to it)
*/
library CryptoHash uses BitInt, MD5
function ApplyHash takes BitInt data returns nothing
local Thread thread = Thread.create()
local BitInt hash
local boolean doSync = true
loop
if (0 != data) then
set hash = MD5(data)
call data.pushFront(hash)
call hash.destroy()
set data = 0
endif
if (doSync) then
call thread.sync()
set doSync = false
endif
call TriggerSyncReady()
exitwhen thread.synced
endloop
call thread.destroy()
endfunction
function ValidateHash takes BitInt data returns boolean
local BitInt hash = data.popFront(16)
local BitInt hash2 = MD5(data)
local integer i = 16
local boolean valid = true
loop
set hash = hash.prev
set hash2 = hash2.prev
if (hash.bits != hash2.bits) then
set valid = false
endif
set i = i - 1
exitwhen 0 == i
endloop
call hash2.prev.destroy()
call hash.prev.destroy()
return valid
endfunction
endlibrary
struct SaveLoadGUI extends array
private static Encoder array encoder
private static method onSave takes nothing returns nothing
local integer playerId = R2I(udg_SL_Save + .5)
local integer encPlayerId = playerId
local Thread thread = Thread.create()
local SaveCode data = 0
local boolean doSync = true
local integer save
set udg_SL_Save = -1
loop
if (GetPlayerId(GetLocalPlayer()) == playerId) then
set data = SaveCode.create()
set playerId = -1
set doSync = false
set save = 0
loop
exitwhen save == udg_SL_SaveCount
if (udg_SL_Item[save] != -1) then
call BitInt(data).write(2, 2)
call data.write(udg_SL_Item[save])
elseif (udg_SL_Unit[save] != -1) then
call BitInt(data).write(3, 2)
call data.write(udg_SL_Unit[save])
else
call BitInt(data).write(1, 2)
call data.write(udg_SL_Integer[save])
endif
set udg_SL_Item[save] = -1
set udg_SL_Unit[save] = -1
set save = save + 1
endloop
set udg_SL_SaveCount = 0
call thread.sync()
elseif (doSync) then
set doSync = false
call thread.sync()
endif
call TriggerSyncReady()
exitwhen thread.synced
endloop
call FormatBitInt.evaluate(data)
if (udg_SL_TamperProtection) then
call ApplyHash(data)
endif
call encoder[encPlayerId].encrypt(data, udg_SL_EncryptionStrength)
call SaveFile(udg_SL_MapName, GetPlayerName(GetLocalPlayer()), data)
if (0 != data) then
call data.destroy()
endif
set udg_SL_OnSaveComplete = -1
set udg_SL_OnSaveComplete = encPlayerId
endmethod
private static method updateProgress takes nothing returns nothing
local integer playerId = 11
loop
set udg_SL_LoadProgress[playerId] = GetLoadProgress(playerId)
exitwhen 0 == playerId
set playerId = playerId - 1
endloop
set udg_SL_UpdateLoadProgress = -1
set udg_SL_UpdateLoadProgress = 1.000
endmethod
private static method run takes nothing returns nothing
local LoadStream stream
local LoadCode data
local timer t
local integer valueType
local boolean doLoad
call WaitForGameToStart()
set udg_SL_StartLoad = -1
set udg_SL_StartLoad = 1.000
set t = CreateTimer()
call TimerStart(t,.03125000,true,function thistype.updateProgress)
set stream = LoadFile(udg_SL_MapName, GetPlayerName(GetLocalPlayer()))
call PauseTimer(t)
call DestroyTimer(t)
set t = null
set udg_SL_LoadingEnabled = File.enabled
set udg_SL_EndLoad = -1
set udg_SL_EndLoad = 1.000
loop
set data = stream.read()
exitwhen 0 == data
call encoder[data.playerId].decrypt(data, udg_SL_EncryptionStrength)
set udg_SL_LoadCount = 0
set doLoad = not udg_SL_TamperProtection or ValidateHash(data)
if (doLoad) then
loop
set valueType = BitInt(data).read(2)
exitwhen 0 == valueType
set udg_SL_Item[udg_SL_LoadCount] = -1
set udg_SL_Unit[udg_SL_LoadCount] = -1
if (valueType == 1) then
set udg_SL_Integer[udg_SL_LoadCount] = data.read()
elseif (valueType == 2) then
set udg_SL_Item[udg_SL_LoadCount] = data.read()
elseif (valueType == 3) then
set udg_SL_Unit[udg_SL_LoadCount] = data.read()
endif
set udg_SL_LoadCount = udg_SL_LoadCount + 1
endloop
set udg_SL_PlayerLoad = -1
set udg_SL_PlayerLoad = data.playerId
endif
call data.destroy()
call TriggerSyncReady()
endloop
call stream.destroy()
endmethod
private static method initItem takes nothing returns nothing
local integer i = 2500
loop
set udg_SL_Item[i] = -1
exitwhen 0 == i
set i = i - 1
endloop
endmethod
private static method initUnit takes nothing returns nothing
local integer i = 2500
loop
set udg_SL_Unit[i] = -1
exitwhen 0 == i
set i = i - 1
endloop
endmethod
private static method init2 takes nothing returns nothing
local integer playerId = 11
local trigger t
set udg_SL_LocalPlayer = GetLocalPlayer()
call initItem.evaluate()
call initUnit.evaluate()
loop
if (GetPlayerSlotState(Player(playerId)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(playerId)) == MAP_CONTROL_USER) then
set encoder[playerId] = Encoder.create(GetPlayerName(Player(playerId)) + udg_SL_Password)
endif
exitwhen 0 == playerId
set playerId = playerId - 1
endloop
call run.execute()
set udg_SL_Save = -1
set t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_SL_Save", GREATER_THAN_OR_EQUAL, 0.00)
call TriggerAddAction(t, function thistype.onSave)
set t = null
endmethod
private static method init takes nothing returns nothing
call DestroyTimer(GetExpiredTimer())
call init2.execute()
endmethod
private static method onInit takes nothing returns nothing
call TimerStart(CreateTimer(),0,false,function thistype.init)
endmethod
endstruct
/*
function SaveItem takes item i, SaveCode data returns nothing
function LoadItem takes LoadCode data returns item
- save/load an item
function SaveInventory takes unit u, SaveCode data returns nothing
function LoadInventory takes unit u, LoadCode data returns nothing
- save/load unit's inventory (up to unit's inventory size)
item charges done as well
function SaveStates takes unit u, SaveCode data returns nothing
function LoadStates takes unit u, LoadCode data returns nothing
- save/load unit health/mana
function SaveStats takes unit u, SaveCode data returns nothing
function LoadStats takes unit u, LoadCode data returns nothing
- save/load str, agi, int (if unit is hero)
function SaveXP takes unit u, SaveCode data returns nothing
function LoadXP takes unit u, LoadCode data returns nothing
- save/load xp (if unit is hero)
function SavePosition takes unit u, SaveCode data returns nothing
function LoadPosition takes unit u, LoadCode data returns nothing
- save/load x, y, and facing
function SaveUnit takes unit u, SaveCode data returns nothing
function LoadUnit takes LoadCode data returns unit
- save/load all of the above
*/
library SaveLoadUnit uses SaveCode, LoadCode
globals
private item loadItem
endglobals
function SaveItem takes item i, SaveCode data returns nothing
call data.write(GetItemTypeId(i))
if (null != i) then
call data.write(GetItemCharges(i))
endif
endfunction
function LoadItem takes LoadCode data returns item
set loadItem = CreateItem(data.read(), 0, 0)
call SetItemCharges(loadItem, data.read())
return loadItem
endfunction
function SaveInventory takes unit u, SaveCode data returns nothing
local integer i = UnitInventorySize(u)
loop
exitwhen 0 == i
set i = i - 1
call SaveItem(UnitItemInSlot(u, i), data)
endloop
endfunction
function LoadInventory takes unit u, LoadCode data returns nothing
local integer i = UnitInventorySize(u)
loop
exitwhen 0 == i
set i = i - 1
call UnitAddItemToSlotById(u, data.read(), i)
if (null != UnitItemInSlot(u, i)) then
call SetItemCharges(UnitItemInSlot(u, i), data.read())
endif
endloop
endfunction
function SaveStates takes unit u, SaveCode data returns nothing
call data.write(R2I(GetWidgetLife(u) + .5))
if (GetUnitState(u, UNIT_STATE_MAX_MANA) > 0) then
call data.write(R2I(GetUnitState(u, UNIT_STATE_MANA) + .5))
endif
endfunction
function LoadStates takes unit u, LoadCode data returns nothing
call SetWidgetLife(u, data.read())
if (GetUnitState(u, UNIT_STATE_MAX_MANA) > 0) then
call SetUnitState(u, UNIT_STATE_MANA, data.read())
endif
endfunction
function SaveStats takes unit u, SaveCode data returns nothing
if (IsUnitType(u, UNIT_TYPE_HERO)) then
call data.write(GetHeroStr(u, false))
call data.write(GetHeroAgi(u, false))
call data.write(GetHeroInt(u, false))
endif
endfunction
function LoadStats takes unit u, LoadCode data returns nothing
if (IsUnitType(u, UNIT_TYPE_HERO)) then
call SetHeroStr(u, data.read(), true)
call SetHeroAgi(u, data.read(), true)
call SetHeroInt(u, data.read(), true)
endif
endfunction
function SaveXP takes unit u, SaveCode data returns nothing
if (IsUnitType(u, UNIT_TYPE_HERO)) then
call data.write(GetHeroXP(u))
endif
endfunction
function LoadXP takes unit u, LoadCode data returns nothing
if (IsUnitType(u, UNIT_TYPE_HERO)) then
call SetHeroXP(u, data.read(), false)
endif
endfunction
function SavePosition takes unit u, SaveCode data returns nothing
call data.write(R2I(GetUnitX(u) + .5))
call data.write(R2I(GetUnitY(u) + .5))
call data.write(R2I(GetUnitFacing(u) + .5))
endfunction
function LoadPosition takes unit u, LoadCode data returns nothing
call SetUnitX(u, data.read())
call SetUnitY(u, data.read())
call SetUnitFacing(u, data.read())
endfunction
globals
private unit loadUnit
endglobals
function SaveUnit takes unit u, SaveCode data returns nothing
call data.write(GetUnitTypeId(u))
call SavePosition(u, data)
if (u != null) then
call SaveInventory(u, data)
call SaveStates(u, data)
call SaveStats(u, data)
call SaveXP(u, data)
endif
endfunction
function LoadUnit takes LoadCode data returns unit
set loadUnit = CreateUnit(Player(data.playerId), data.read(), data.read(), data.read(), data.read())
if (loadUnit != null) then
call LoadInventory(loadUnit, data)
call LoadStates(loadUnit, data)
call LoadStats(loadUnit, data)
call LoadXP(loadUnit, data)
endif
return loadUnit
endfunction
endlibrary
Be sure to read all trigger comments in the GUI Triggers to understand how to
use this system
The demo provided is a simple save/load setup that can save infinite
units (unit, xp, health, mana, items, item charges)
Notes on Variables
SL_EncryptionStrength
Set this to how strong you want the encryption to be. Encryption strength
goes from 0 to 16, 0 being no encryption and 16 being max encryption.
The encryption even at 1 is very strong, and stronger encryption means
that saving and loading will take longer (possible freezes depending on
data size). I personally recommend an encryption strength of 2. Anything
more is overkill.
SL_MapName
Set this to the name of your map (or group of maps). This will save and
load data specific to your map.
SL_Password
This is a map unique password for the encryption process. Be sure to set
this to a value. Both the player name and this password are used in
encryption, so the codes generated on the player's HDD are unique to a
specific account. There is no need to save a player's name in the code.
This will only be used if the encryption strength is greater than 0
SL_TamperProtection
This ensures that the player can't randomly change the code on their HDD.
This is what validates* the code. The encryption is what makes the code
impossible to read. I recommend that you always have this on, even with
no encryption.
SL_PlayerLoad is set to the JASS Player Id of the loading player (player id 0 == Player 1)
Players will auto load all information at the start of the map
The loaded information is stored into 1 of 3 arrays.
SL_LoadInteger - integer values
SL_LoadItem - item type id values
SL_LoadUnit - unit type id values
The load will only run if the player successfully loaded.
A player will not load in the event of loading being disabled, the player not having any save/load code, or the save/load code being invalid
The information contained in these 3 arrays is identical to the information that was written to them during saving
SL_LoadCount stores how many values have been saved.
The values range from 0 to SL_LoadCount - 1
By default, SL_LoadUnit and SL_LoadItem will be values of -1. If one of these values aren't -1, then an item or unit was saved. If they are both -1, an integer was saved.
Below, I use a variable called SL_LoadIterator in order to iterate from 0 to SL_LoadCount - 1
Notice that whenever I read a value, I increase SL_LoadIterator. At the end of the loop, I decrease it by 1 (since the loop itself increases by 1).
Game - SL_PlayerLoad becomes Greater than or equal to 0.00
Conditions
Actions
Unit Group - Pick every unit in (Units in (Playable map area)) and do (Actions)
Loop - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Owner of (Picked unit)) Equal to (Player(((Integer(SL_PlayerLoad)) + 1)))
Then - Actions
Unit - Remove (Picked unit) from the game
Else - Actions
Set SL_LoadIterator = SL_LoadCount
For each (Integer SL_LoadIterator) from 0 to (SL_LoadCount - 1), do (Actions)
Loop - Actions
-------- Load The Unit --------
Unit - Create 1 SL_Unit[SL_LoadIterator] for (Player(((Integer(SL_PlayerLoad)) + 1))) at ((Player(((Integer(SL_PlayerLoad)) + 1))) start location) facing Default building facing degrees
Set SL_LoadIterator = (SL_LoadIterator + 1)
-------- Load Unit Life --------
Unit - Set life of (Last created unit) to (Real(SL_Integer[SL_LoadIterator]))
Set SL_LoadIterator = (SL_LoadIterator + 1)
-------- If Unit Has Mana, Load Mana --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Max mana of (Last created unit)) Greater than 0.00
Then - Actions
Unit - Set mana of (Last created unit) to (Real(SL_Integer[SL_LoadIterator]))
Set SL_LoadIterator = (SL_LoadIterator + 1)
Else - Actions
-------- If Unit Is A Hero, Load XP --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
((Last created unit) is A Hero) Equal to True
Then - Actions
Hero - Set (Last created unit) experience to SL_Integer[SL_LoadIterator], Hide level-up graphics
Set SL_LoadIterator = (SL_LoadIterator + 1)
Else - Actions
-------- If Unit Has An Inventory, Load Inventory --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Size of inventory for (Last created unit)) Greater than 0
Then - Actions
For each (Integer B) from 1 to (Size of inventory for (Last created unit)), do (Actions)
Loop - Actions
-------- If There Is An Item In Slot, Load Item + Charges --------
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
SL_Integer[SL_LoadIterator] Greater than 0
Then - Actions
Item - Create SL_Item[SL_LoadIterator] at (Position of (Last created unit))
Set SL_LoadIterator = (SL_LoadIterator + 1)
Item - Set charges remaining in (Last created item) to SL_Integer[SL_LoadIterator]
Set SL_LoadIterator = (SL_LoadIterator + 1)
Hero - Give (Last created item) to (Last created unit)
Else - Actions
Set SL_LoadIterator = (SL_LoadIterator + 1)
Else - Actions
-------- This Is Decreased By 1 As The Loop Increases It By 1 --------
This is run when loading loading ends
This is useful for cleaning up text tags/multiboard or for clearing text messages on % download complete
This is also useful for displaying whether the user has loading enabled or not
If SL_LoadingEnabled is true, the user can load
If it's false, the user can't load
In order to enable loading in the event that they can't load
1. Go to C drive
2. Go to !! AllowLocalFiles folder (at the very top of the drive)
3. Run AllowLocalFiles.bat
4. Restart Warcraft 3
The user will only not have loading enabled. They will still be able to save.
If they don't have loading enabled, they should do steps 1-3 and then play the map normally. After the game ends, they should restart Warcraft 3 before joining another game.
This only ever has to be done 1 time for a machine. This also only works on Windows. By using this save/load system, your map will not be playable on macs, which isn't a big deal.
Be sure to display these steps somewhere for the user!!!!
Saving can either be done with a -save command or a periodic timer (periodic saving means no need for the player to save + can allow player trading w/o worry of players duplicating items)
Saving is not instant! If a player leaves during saving, they risk corrupting their save/load code.
Be sure to let the player know that they should only leave after successfully saving. A saved status should be displayed somewhere. In this demo, I just use a Save Completed message.
In this demo, I use a boolean SL_IsSaving so that players can't run the save command while they are already saving.
When saving an integer, save to SL_Integer.
When saving a unit, save to SL_Unit
When saving an item, save to SL_Item
Always increase SL_SaveCount by 1 after saving
Notice this line
(Triggering player) Equal to SL_LocalPlayer
This is done in an if statement above the actual saving. Saving is done for only 1 player, so you always need this if statement.
Furthermore
Wait 0.00 seconds
Because saving is only done for 1 player (meaning only run on that one player's machin), you need to give the player a chance to perform the save. The Wait 0 seconds gives them that chance.
At the end of the save
Set SL_Save = (Real(((Player number of (Triggering player)) - 1)))
You need to set SL_Save to the JASS Player Id of the saving player. This will actually execute the save.
The value SL_OnSaveComplete is set to is the JASS Player Id of the saved player.
With this, you can display to that saved player that they have successfully finished saving and that it is now safe to leave the game.
You should also notice that I set SL_IsSaving to false here. This means that the player may now use the -save command again.
If doing periodic saving, the periodic save will now start saving for the player again.
A bat file must be run in order to enable loading (notes in map)
The save/load system provided is meant to be used to craft map specific save/load systems
The GUI Save/Load system is only meant for simple uses. For more complex uses, dangers outlined in vJASS tutorial should be fixed
The GUI Save/Load system does not support multi profiling. To support it, another GUI save/load system needs to be written
The GUI Save/Load system provided is not the be all and end all to GUI save/load systems, it is a very simple implementation. Other GUI save/load systems using the code in this map can and should be coded.
Credits to Dr Super Good for aiding in step 1 of Decoder getNextDataSet set dataSet = (remainingData/8 - 16) - (((remainingData/8 - 16)/AES_INTERVAL)*AES_INTERVAL)
I know it is probably something ludicrously simple and I am not doing something right, but I've read through this three or four times and I still don't understand how to use it.
While I can just copy it all over, it doesn't do anything. More specifically I do not know how it works or how to make it work.
I don't recommend using codeless save/load until the synchronization is optimized. It'll kill your map. That is, unless your ok with players chilling for 5-20 minutes, unable to do anything, while the thing synchronizes : P.
TriggerHappy has an easy to use save/load system with GUI support. My traditional one is vJASS only, which is probably why Chaosy linked the codeless one.
If you're interesting in still using the codeless save/load in the hopes that the thing'll get optimized before your map is released (the API's not going to change, so you won't have to change your code), then you can go ahead and use it. However, I make no promises >: O.
You've got 3 arrays of values in the GUI demonstration. You have 1 integer that you keep incrementing every time you read/write to the array.
For example, if you want to save 3 values using the GUI stuff
Events
Player - Player 12 (Brown) types a chat message containing -save as An exact match
Conditions
SL_IsSaving[(Player number of (Triggering player))] Equal to False
Actions
Set SL_IsSaving[(Player number of (Triggering player))] = True
-------- The values of 5, 6, and 7 are saved --------
Set SL_Integer[SL_SaveCount] = 5
Set SL_SaveCount = (SL_SaveCount + 1)
Set SL_Integer[SL_SaveCount] = 6
Set SL_SaveCount = (SL_SaveCount + 1)
Set SL_Integer[SL_SaveCount] = 7
Set SL_SaveCount = (SL_SaveCount + 1)
-------- Save the above values for the player that wrote the -save command --------
Set SL_Save = (Real(((Player number of (Triggering player)) - 1)))
As for how to run it, well.. the top of the OnSave trigger should show you : p
OnSave
Events
Player - Player 1 (Red) types a chat message containing -save as An exact match
The only thing you need to load it is
OnLoad
Events
Game - SL_PlayerLoad becomes Greater than or equal to 0.00
Conditions
Actions
Set Int1 = SL_Integer[0]
Set Int2 = SL_Integer[1]
Set Int3 = SL_Integer[2]
Now when you first run it, the FileIO lib will probably tell you that you can't read files. It's going to tell you to run a script that will be in like your main directory on your hard drive or something. Just alt tab out of warcraft 3, open file explorer, then run the script. After that, you'll be gold. You'll be able to load your code the next time you play the map : ). The map should only state it the first time ever, so in that game, you won't have a code to load yet ^_^.
There are a whole bunch of other events you can hook into. For example, you can show a player's download progress (% of load completion).
Under the variable list, you also have a few cool variables to set
SL_EncryptionStrength
Set this to how strong you want the encryption to be. Encryption strength
goes from 0 to 16, 0 being no encryption and 16 being max encryption.
The encryption even at 1 is very strong, and stronger encryption means
that saving and loading will take longer (possible freezes depending on
data size). I personally recommend an encryption strength of 2. Anything
more is overkill.
SL_MapName
Set this to the name of your map (or group of maps). This will save and
load data specific to your map.
SL_Password
This is a map unique password for the encryption process. Be sure to set
this to a value. Both the player name and this password are used in
encryption, so the codes generated on the player's HDD are unique to a
specific account. There is no need to save a player's name in the code.
This will only be used if the encryption strength is greater than 0
SL_TamperProtection
This ensures that the player can't randomly change the code on their HDD.
This is what validates* the code. The encryption is what makes the code
impossible to read. I recommend that you always have this on, even with
no encryption.
I believe the code should also be organized into like a "required" folder, so you can mostly just cnp the folders to your map. The GUI demonstration is just a demonstration of how to use the GUI save/load system.
Now, if you are not hopeful that I am going to optimize the synchronization before your map is released, and believe me, I probably won't, you can opt for traditional save/load. TriggerHappy has a lib in Spells section. I also have a save/load with snippets map. My traditional one is NOT GUI. The traditional one can be found here -> https://github.com/nestharus/JASS/b...Load/Save and Load With Snippets.w3x?raw=true
and the post
This is a pack of resources that makes creating a high quality save/load system easy. It includes systems, snippets, tutorials, and demonstrations.
Saving An Inventory
Saving Item Charges
Saving Hero
Saving Multiple Units
The Architecture of Save/Load
The Architecture of Versioned Save/Load
Saving Infinite Unique Units
Saving Player Resources
Conditional Saving
Saving Partial Slotted Inventories
save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load save load
And finally, here is the traditional one by TriggerHappy : ). The codes it outputs will be longer and they will be easier to crack, but beggars can't be choosers : P.
edit
And oh snap, I see bugs in my GUI Demonstration, haha... shows you how half-***ed I did it : p. If more than 1 player saves at a time, gg . GUI is just a pain and I hate having to deal with it :\. The vJASS version of this is much better.
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.