• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Quickstart Guide to Saving and Loading with Encoder

Level 31
Joined
Jul 10, 2007
Messages
6,306
Needs an update, will update some time soon

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
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-
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:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Graveyard this please, I'm updating it to be like a demo map sorta deal =P, that way users can just cnp the various categories rather than going through all of this nonsense.

I'm moving the entire tutorial into the map >: O.

I guess I'll submit it to... spells section as a demo map? lol


or where.. hmm


maybe I'll submit it as Save/Load Framework >: D
 
Top