library Encoder /* v3.0.1.2
*************************************************************************************
*
* Save/Load system
*
*************************************************************************************
* */uses/*
*
* */ BigInt /* hiveworkshop.com/forums/jass-functions-413/system-bigint-188973/
* */ QueueQueue /* hiveworkshop.com/forums/submissions-414/snippet-queuequeue-190890/
*
* These two can be changed (*Advanced*)
* */ KnuthChecksum /* hiveworkshop.com/forums/1846246-post343.html
* */ Scrambler /* hiveworkshop.com/forums/submissions-414/snippet-salt-189766/
*
* Used in settings functions:
* private function Checksum takes BigInt k, integer m returns integer
* private function ApplyScramble takes BigInt k, integer pid returns nothing
* private function UnapplyScramble takes BigInt k, integer pid returns nothing
*
************************************************************************************
*
* SETTINGS
*/
private keyword b10 //base 10
globals
/*************************************************************************************
*
* I suggest permutation of the following base for encoders
*
* 0123456789ABCDEFGHKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@#$%&?
*
*************************************************************************************/
/*************************************************************************************
*
* VER_BASE refers to the base used with encoder.toString()
*
*************************************************************************************/
private constant string VER_BASE="0123456789"
/*************************************************************************************
*
* Coloring Settings
*
*************************************************************************************/
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 DELIMITER_COLOR="|cffffffff" //what to color DELIMITER characters
/*************************************************************************************
*
* Spacing Settings
*
*************************************************************************************/
private constant string DELIMITER="-" //DELIMITER to make code easier to read
private constant integer DELIMITER_COUNT=4 //how many characters per DELIMITER
/*************************************************************************************
*
* Encryption Settings
*
*************************************************************************************/
/*************************************************************************************
*
* SHUFFLES
*
* How many shuffles to perform in encoder
*
* Nust be greater than 0
*
*************************************************************************************/
private constant integer SHUFFLES=3
/*************************************************************************************
*
* CHECKSUM_VARIANCE
*
* Balanced value: .85
*
* 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.
*
* Ex:
* .85 for a checksum of 238,609,294
*
* min: 202,817,899
* range: 35,791,395
*
* 1 in 35,791,395 checksums will work for a player and checksums will all be around same
* strength.
*
* .99 for a checksum of 238,609,294
*
* min: 236,223,201
* range: 2,386,093
*
* 1 in 2,386,093 checksums will work for a player and checksums will all be around same
* strength.
*
* .01 for a checksum of 238,609,294
*
* min: 2,386,092
* range: 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
/*************************************************************************************
*
* PLAYER_CHECKSUM_SALT
*
* Player checksum salt refers to a value that is appended to a player's name when
* generating player hashes. A player's checksum salt helps determine the player's
* checksum for encoders.
*
* This value can be any string
*
* example: "29a\\~alf1!m~..."
*
*************************************************************************************/
private constant string PLAYER_CHECKSUM_SALT=""
endglobals
/*************************************************************************************
*
* Checksum
*
* This is the Checksum used for code security (makes modifications
* difficult). By default, this uses the Knuth Checksum, but
* that can be changed.
*
* BigInt k: number to get the checksum for
* integer m: dividend for modulos
*
* returns: nothing
*
*************************************************************************************/
private function Checksum takes BigInt k, integer m returns integer
return GetKnuthChecksum(k, m)
endfunction
/*************************************************************************************
*
* ApplyScramble
*
* This is essentially for scrambling the code given a player id.
* By default this uses my own scrambling algorithm.
* Because a player hash based on the player's username is used in my
* scrambling algorithm, this will make it so that a player can't load
* up the code of another player with 0 increase to the code size.
* This security alone is not enough to 100% guarantee the player unique
* codes.
*
* BigInt k: number to be scrambled
* integer pid: player id to scramble for
*
* returns: nothing
*
*************************************************************************************/
private function ApplyScramble takes BigInt k, integer pid returns nothing
call Shuffle(k, pid, SHUFFLES)
endfunction
/*************************************************************************************
*
* UnapplyScramble
*
* This is used to undo the scrambling on a number. This should
* revert the number back to what it was before it was scrambled.
*
* BigInt k: number to be unscrambled
* integer pid: player id to unscramble the number for
*
* returns: nothing
*
*************************************************************************************/
private function UnapplyScramble takes BigInt k, integer pid returns nothing
call Unshuffle(k, pid, SHUFFLES)
endfunction
/*
*******************************************************************
*
* struct CodeRange extends array
*
* - A slot that can store a value. These slots have a range of values they can store. The
* - range is from a low bound value to a high bound value. Slots are added to Encoder objects.
* - Specific ranges of slots can link to other slots (an item with 25 charges for example). Not
* - all slots can store values: some are purely pointers (an inventory slot for example which simply
* - points to 6 items).
*
* static method create takes integer lowBound, integer highBound returns CodeRange
* - Creates a new CodeRange that can be added to an Encoder and linked to. CodeRange objects
* - can be linked to multiple times and can link to as many other CodeRange objects as needed.
* - If the lowBound is equal to the highBound, then the CodeRange object returned is a pointer
* - object that can't store values. This can be useful for things like an inventory that simply
* - points to 6 item slots.
*
* - integer lowBound The minimum value that can be stored in the slot.
* - integer highBound The maximum value that can be stored in the slot.
* method link takes integer lowBound, integer highBound, CodeRange linkTo, integer customLinkId returns nothing
* - Links a CodeRange to another CodeRange. The link is only applied if the value that ends
* - up going into the CodeRange fits the link range. For example, if only heroes from 1 to 5 had
* - an inventory of 6, then the lowBound would be 1 and the highBound would be 5 for that link.
* - Passing in the minimal value and maximal values for a given slot does a link for all possible
* - values that can go into that slot.
*
* - integer lowBound The minimal value that can be in the slot to go into the link
* - integer highBound The maximal value that can be in the slot to go into the link
* - CodeRange linkTo The slot that is to be linked to
* - integer customLinkId A link id that can be used to infer current slot for save/load
* method linka takes CodeRange linkTo returns nothing
* - Links all possible values to a slot. Essentially just calls link with the minimum possible
* - value, maximum possible value, and a custom id of 0.
*
* - CodeRange linkTo The slot that is to be linked to
*
************************************************************************************
*
* struct DataBuffer extends array
*
* - The DataBuffer is used for reading and writing values.
* - When opening an Encoder, it is loaded into the DataBuffer
* - and then values can be read/written. An Encoder may be
* - opened for decompressing a code or for compressing a
* - collection of numbers into a code.
*
* readonly integer id
* - Returns the current custom link id (remember link and linka in CodeRange)
* - As slots inside of links may or may not exist (does value fit?), this is
* - a necessity so that a user can easily determine whether they are in a
* - questionable slot or not.
* - Can be used in read and write mode.
* readonly string code
* - Returns all of the values in the DataBuffer as a save/load code.
* - Can only be used when the DataBuffer is finalized.
* - Can be used in write mode.
*
* method write takes integer value returns nothing
* - Writes a value to the DataBuffer. Order of values is determined by the
* - loaded Encoder. For example, if a Hero, gold, and lumber slots were
* - added to the Encoder in that order, then the DataBuffer would expect
* - values fitting those ranges in that order. When all values are written,
* - the DataBuffer is finalized (meaning can't be written to) and the code
* - can be read.
* - Can be used in write mode.
*
* - integer value The value to write to the DataBuffer.
* method read takes nothing returns integer
* - Reads a value out of the DataBuffer. Value read order is determined by
* - the loaded Encoder.
* - Can be used in read mode.
*
************************************************************************************
*
* struct Encoder extends array
*
* - An Encoder is like a frame for compacting values. It is used
* - for storing base, checksum, player checksum, and CodeRange information.
* - the Encoder determines the order of values in a code for the DataBuffer
* - as well. DataBuffers can only be opened through an Encoder.
*
* static method create takes string base, integer minCodeLength, integer maxCodeLength, integer maxChecksum, integer encoderVersion returns Encoder
* - Creates a new Encoder.
*
* - string base The collection of possible characters that the Encoder can
* - use for its save/load codes. Bigger collection means smaller
* - codes.
* - integer minCodeLength Minimum length a code has to be to be loaded. Useful for blocking
* - small values like 1 or 5. Keeps load codes in bounds.
* - integer maxCodeLength Maximal length a code can be to be loaded. Useful for blocking
* - off random large values. Keeps load codes in bounds.
* - integer maxChecksum The maximum checksum value that can be put into the Encoder.
* - Checksums are used to validate codes (ensure they weren't
* - tampered with and that there are no typos in it). The bigger
* - the checksum value, the more secure the code is, but the longer
* - the code will be. I typically use a 6 digit number like 148292
* - or 559321.
* - Maximum checksum- 238609294
* -
* - integer encoderVersion Used for version control on encoders. toString returns this value and
* - convertString takes the toString value and converts it back into the encoder
* - toString() -> encoderVersion
* - convertString(encoderVersion)
* method toString takes nothing returns string
* - Converts the Encoder into a string that represents it. This can
* - be outputted to players to show the Encoder version that their
* - code was saved for. Players can then possibly type in the Encoder
* - version of older codes so that older codes can be loaded.
* static method convertString takes string encoderId returns Encoder
* - Converts an Encoder string into an Encoder. Used primarily for
* - older save/load codes (player might have inputted Encoder id
* - for their save/load code).
*
* - string encoderId The string that represents the Encoder (toString).
* method add takes CodeRange valueSlot returns nothing
* - Adds a new slot to the Encoder. Value order doesn't matter.
*
* - CodeRange valueSlot The CodeRange to be added to the Encoder.
* method read takes string codeString, integer loadingPlayerId returns DataBuffer
* - Opens a DataBuffer for reading and returns the opened DataBuffer. Loads
* - a code string into the DataBuffer. If this returns 0, the code was invalid.
*
* - string codeString The code to load into the DataBuffer
* - integer loadingPlayerId The player id to load the code for (must
* - be a valid human playing player).
* method write takes integer savingPlayerId returns DataBuffer
* - Opens a DataBuffer for writing and returns the opened DataBuffer. If
* - this returns 0, the Encoder or player id were invalid.
*
* - integer savingPlayerId The player id to save the code for (must
* - be a valid human playing player).
*
************************************************************************************/
/*************************************************************************************
*
* Code
*
*************************************************************************************/
globals
private keyword Link
/*************************************************************************************
*
* Encoder Variables
*
*************************************************************************************/
private Table array eb //encoder base
private string array er //encoder code string
private Table array eh //encoder max hash value
private Base es //encoder base for code string
private Link array el //last range added to encoder
private integer array ec //encoder ver to encoder
private integer array ml //minimum code length
private integer array mx //maximum code length
/*************************************************************************************
*
* Range Variables
*
*************************************************************************************/
private integer array rl //low bound
private integer array rh //high bound
private integer array rsh //shifted high bound
private integer array rf //flag
private constant integer AP=1 //always positive
private constant integer SN=2 //sometimes negative
private constant integer AN=3 //always negative
/*************************************************************************************
*
* Link Variables
*
*************************************************************************************/
private integer array li //link id
private boolean array lb //is link
/*************************************************************************************
*
* Player Variables
*
*************************************************************************************/
private integer array ph //hash of player name + salt
private integer array pn //next player
/*************************************************************************************
*
* Data Buffer Variables
*
*************************************************************************************/
//Base
private Base b10=0 //use for writing
//use encoder base for reading
private Link array dm //data buffer looper
private integer dc=0 //data buffer count
private integer array dn //data buffer next, recycler
private integer array dl //data buffer previous
private integer array dv //data buffer value
private Link array di //data buffer link node id
private integer array dd //data buffer node
private integer array dz //data buffer link id
private integer array de //data buffer encoder
private boolean array df //is data buffer finalized?
private boolean array dw //data buffer open for writing?
private integer array dp //data buffer player
endglobals
/*************************************************************************************
*
* Link
*
* Links values together to form dynamic objects.
*
*************************************************************************************/
private struct Link extends array
implement QueueQueue
implement QueueQueueLoop
endstruct
/*************************************************************************************
*
* CodeRange : Link
*
* Used for manipulating value slots in Encoders and CodeRanges.
*
* Methods
* static method create takes integer l, integer h returns CodeRange
* method link takes integer l, integer h, CodeRange p, integer i returns nothing
* method linka takes CodeRange p returns nothing
*
*************************************************************************************/
struct CodeRange extends array
/*************************************************************************************
*
* create
* Creates a code range given a maximum value and a minimum value. Ranges are slots
* that can be added to encoders and other Ranges.
*
* integer l: low bound
* integer h: high bound
*
* returns: CodeRange
*
*************************************************************************************/
static method create takes integer l,integer h returns CodeRange
local Link t
if (h>l) then
//first ensure that the high bound is greater than the low bound
// if the high bound is greater, then it is a valid range for
// storing actual values
//instantiate
set t=Link.allocate()
set t.skips=false
//store the low bound and the high bound into the properties
set rl[t]=l //low bound
set rh[t]=h //high bound
//now have to determine how to store the value
//the value could be negative, it could always be negative,
//or it could always be positive
if (0>h) then
/**********************************
*
* Flag: AN
* Shifted Low Bound: High Bound
* Shifted High Bound: -Low Bound + High Bound
*
**********************************/
set rf[t]=AN //flag to always negative
//the shifhted high bound is the low bound minus
//the high bound
set rsh[t]=-l+h+1
elseif (0>l) then
/**********************************
*
* Flag: SN
* Shifted Low Bound: low bound
* Shifted High Bound: high bound - low bound
*
**********************************/
set rf[t]=SN //flag to sometimes negative
set rsh[t]=h-l+1
else
/**********************************
*
* Flag: AP
* Shifted Low Bound: lowBound
* Shifted High Bound: highBound-lowBound
*
**********************************/
set rf[t]=AP
set rsh[t]=h-l+1
endif
return t
elseif (h==l) then
//if they are equal, then it is a valid pointer range.
// pointer ranges are used to just point to values. They don't go into the actual code,
// but values they point to do.
/**********************************
*
* Flag: 0
*
**********************************/
//simple instiate it as it is meant only for pointing
return Link.allocate()
debug else
//if the high bound is lower than the low bound, then the range isn't valid
// throw an error
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"CODE RANGE CREATE ERROR: HIGH BOUND LOWER THAN LOW BOUND")
debug return 0
endif
return 0
endmethod
/*************************************************************************************
*
* link
* Links a CodeRange to another CodeRange by adding the later range to the
* first range by a pointer.
*
* integer l: low bound
* integer h: high bound
* integer p: CodeRange to add to CodeRange this
* integer i: a user id for identifying the link
*
* returns: nothing
*
*************************************************************************************/
method link takes integer l,integer h,CodeRange p,integer i returns nothing
local Link t
debug if (h>=l and h<=rh[this] and l>=rl[this]) then
//first ensure that the high bound is greater than the low bound
// if the high bound is greater, then it is a valid range for
// storing actual values
//instantiate via point
set t=Link(this).point(p)
set t.skips=false
set rl[t]=l //link low bound
set rh[t]=h //link high bound
set li[t]=i //link id for identification
set lb[t]=true //is link
debug else
//if the high bound is lower than the low bound, then the range isn't valid
// throw an error
debug if (l>h) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"CODE RANGE CREATE ERROR: HIGH BOUND LOWER THAN LOW BOUND")
debug endif
//range was out of bounds
debug if (h>rh[this] or l<rl[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"CODE RANGE CREATE ERROR: LINK RANGE OUT OF BOUNDS")
debug endif
debug endif
endmethod
/*************************************************************************************
*
* linka
* A special link that links all values (lowBound to highBound).
*
* integer p: CodeRange to add to CodeRange this
*
* returns: nothing
*
*************************************************************************************/
method linka takes CodeRange p returns nothing
call link(rl[this],rh[this],p,0)
endmethod
endstruct
/*************************************************************************************
*
* DataBuffer : Queue, Link
*
* Used to read/write code.
*
* Properties
* readonly integer id
*
* Methods
* method write takes integer value returns nothing
* method operator code takes nothing returns string
* method read takes nothing returns integer
* internal method open takes nothing returns nothing
*
*************************************************************************************/
private keyword open
struct DataBuffer extends array
/*************************************************************************************
*
* id
* Retrieves the current open id on the DataBuffer
*
*************************************************************************************/
method operator id takes nothing returns integer
return dz[this]
endmethod
/*************************************************************************************
*
* internal open
* Prepares next slot in DataBuffer
*
* takes: nothing
*
* returns: nothing
*
*************************************************************************************/
method open takes nothing returns nothing
local Link n
local Link y=0
local Link l=0
//retrieve the next node
set n=dm[this].get //node
loop
//if the current node is a pointer, have to determine
// whether to go inside of the pointer or not.
exitwhen not lb[n]
loop
//keep looping until can either go inside of link or the
// node isn't a link
//link ranges of 0 to 0 are pointer links, all values fit into them
exitwhen not lb[n] or (0==rl[n] and 0==rh[n]) or 0==n
//if not a pointer link, then have to retrieve the parent
// node's value
set y=dm[this].depthPointer
loop
exitwhen not lb[y.depthNode]
set y=y.depthPointer
endloop
//the value is stored into the depth pointer's id, check for fit
exitwhen (y.id>=rl[n] and y.id<=rh[n])
set n=dm[this].skip //if value doesn't fit, skip link and all of its contents
endloop
//if the final found node was a pointer (meaning the value fit)
//go inside of it
if (lb[n]) then
//only store link for node ref if the link id isn't 0
if (0!=li[n]) then
set l=n
endif
set n=dm[this].get //go to next node
endif
endloop
//if there is no next node, finalize
if (0==n) then
set df[this]=true
return
endif
//add new node to data buffer
if (0==dn[0]) then
set dc=dc+1
set y=dc
else
set y=dn[0]
set dn[0]=dn[y]
endif
//this buffer is a list because it will eventually have to be looped
//over backwards. The buffer has to be read into the code backwards
//or it will be impossible to read it out because some values exist
//and some don't. Remember integers are written left to right but
//read right to left.
set dl[y]=dl[this]
set dn[y]=this
set dn[dl[y]]=y
set dl[this]=y
//set link id for current node to the node right above it, which
// is always going to be a link
set di[y]=li[l]
set dz[this]=li[l]
//set node for size reference and shifting
set dd[y]=n
endmethod
/*************************************************************************************
*
* write
* Writes a value to the DataBuffer
*
* integer value: value to write
*
* returns: nothing
*
*************************************************************************************/
method write takes integer v returns nothing
local Link y=dl[this]
local Link n=dd[y]
//make sure
// buffer isn't finalized
// buffer is open for writing
// buffer is valid
debug if (not df[this] and dw[this] and 0!=de[this] and v>=rl[n] and v<=rh[n]) then
//store shifted value as current depth pointer id
set dm[this].depthPointer.id=v
//shift the value so that it is smaller
if (rf[n]==AN) then
set v=-v+rh[n]
else
set v=v-rl[n]
endif
//store value
set dv[y]=v
//prepare next slot for writing
call open()
debug else
debug if (v<rl[n] or v>rh[n]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: VALUE OUT OF BOUNDS\n "+I2S(v)+"-> "+I2S(rl[n])+" ... "+I2S(rh[n]))
debug endif
debug if (df[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: DATA BUFFER FINALIZED")
debug endif
debug if (not dw[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: DATA BUFFER NOT OPEN FOR WRITING")
debug endif
debug if (0==de[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: INVALID ENCODER")
debug endif
debug endif
endmethod
/*************************************************************************************
*
* read
* Reads a value out of the DataBuffer
*
* returns: integer
*
*************************************************************************************/
method read takes nothing returns integer
local Link n
local Link o
//make sure
// buffer isn't finalized
// buffer is open for reading
// buffer is valid
debug if (not dw[this] and 0!=de[this]) then
//retrieve current node
set n=dp[this]
set o=dn[n]
//go to next node
set dp[this]=o
set dz[this]=di[o]
//if no more nodes, deallocate and close
if (o==this) then
set de[this]=0
set dn[dl[this]]=dn[0]
set dn[0]=this
endif
return dv[n]
debug else
debug if (dw[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: DATA BUFFER NOT OPEN FOR READING")
debug endif
debug if (0==de[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: INVALID ENCODER")
debug endif
debug endif
return 0
endmethod
/*************************************************************************************
*
* code
* Converts all values in a finalized DataBuffer that is open for writing
* into a save/load code and returns that code.
*
* returns: string
*
*************************************************************************************/
method operator code takes nothing returns string
local BigInt i //code as a number
local integer h //hash
local integer n //node
local string s="" //colorized code string
local string c //character
local boolean l //lowercase
local Base b
//make sure
// buffer is finalized
// buffer is open for writing
// buffer is valid
debug if (df[this] and dw[this] and 0!=de[this]) then
//create the code integer
set i=BigInt.create(b10)
//compress the values into one value
set n=dl[this]
loop
call i.multiply(rsh[dd[n]])
call i.add(dv[n],0)
set n=dl[n]
exitwhen n==this
endloop
//apply checksum
set h=Checksum(i,eh[de[this]][dp[this]])
//if checksum > last value
// range
if (integer(eh[de[this]][dp[this]])>rsh[dd[dl[this]]]) then
//add to front. Have to rebuild the entire number.
call i.destroy()
set i=BigInt.create(b10)
call i.add(h,0)
//compress the values into one value
set n=dl[this]
loop
call i.multiply(rsh[dd[n]])
call i.add(dv[n],0)
set n=dl[n]
exitwhen n==this
endloop
else
//multiply to back
call i.multiply(eh[de[this]][dp[this]])
call i.add(h,0)
endif
//scramble
call ApplyScramble(i,dp[this])
//colorize
set b=eb[de[this]][dp[this]]
set i.base=b
set h=DELIMITER_COUNT
loop
set i=i.previous
exitwhen i.end
set c=b.char(i.digit)
if (0==h) then
set h=DELIMITER_COUNT
set s=s+DELIMITER_COLOR+DELIMITER
endif
set l=StringCase(c,false)==c
if (c==StringCase(c,true) and l) then
if ("0"==c or 0!=S2I(c)) then
//number
set s=s+NUM_COLOR+c
else
//special
set s=s+SPEC_COLOR+c
endif
elseif (l) then
//lower
set s=s+LOWER_COLOR+c
else
//upper
set s=s+UPPER_COLOR+c
endif
set h=h-1
endloop
//close
call i.destroy()
set df[this]=false
set dw[this]=false
set de[this]=0
//deallocate
set dn[dl[this]]=dn[0]
set dn[0]=this
return s+"|r"
debug else
debug if (not df[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: ATTEMPT TO READ PARTIAL BUFFER")
debug endif
debug if (not dw[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: DATA BUFFER NOT OPEN FOR WRITING")
debug endif
debug if (0==de[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"DATA BUFFER ERROR: INVALID ENCODER")
debug endif
debug endif
return null
endmethod
endstruct
/*************************************************************************************
*
* Encoder : Link
*
* Used for forming a frame for generating codes
*
* Methods
* static method create takes string base, integer minCodeLength, integer maxCodeLength, integer maxHash, integer ev returns Encoder
* method toString takes nothing returns string
* static method convertString takes string encoderId returns Encoder
* method add takes CodeRange range returns nothing
* method read takes string s, integer p returns DataBuffer
* method write takes integer p returns DataBuffer
*
*************************************************************************************/
struct Encoder extends array
/*************************************************************************************
*
* create
* Creates a new Encoder given a base (characters used in code), a minimum
* code length, a maximum code length, a maximum hash (unique codes),
* and an encoder version.
*
* string b: The haracters used
* Must exist
* integer u: Min code length
* integer x: Max code length
* integer mh: The maximum hash the Encoder can have. Bigger is more unique
* and secure codes, but longer codes.
* Must be > 1
* integer ev: The version of the encoder object
*
* returns: Encoder
*
*************************************************************************************/
static method create takes string b,integer u,integer x,integer mh,integer ev returns thistype
local integer t=StringLength(b) //the Encoder
local string s //Encoder as a string
local string c //Encoder string character
local integer h //string length of Encoder as string
local boolean l //for colorizing (is lower case?)
local integer i=pn[16] //for looping through players
//checksum values
local integer m //min checksum
local integer r //checksum range
//base values
local Base y //original base
local BigInt q //original base int
local BigInt q2 //new base
//first ensure that the base and max checsum are valid
debug if (1<t and 1<mh and x>=u) then
//checksum values
set m=R2I(mh*CHECKSUM_VARIANCE) //min checksum
set r=mh-m //range
//base values
set y=Base[SubString(b,1,2)+SubString(b,0,1)+SubString(b,2,t)] //original base
set q=BigInt.convertString(b,y) //original base int
//instantiate a new Encoder
set t=Link.allocate() //create encoder
set eh[t]=Table.create() //encoder checksum table (different for each player)
set eb[t]=Table.create() //encoder base table (different for each player)
set ml[t]=u //min code length
set mx[t]=x //max code length
//generate checksums and bases for each player
loop
//generate player base
set q2=q.copy() //copy original base
call Scramble(q2,i,3,y,true) //scramble it
set eb[t][i]=Base[q2.toString()] //give to player
call q2.destroy() //clean
//generate player checksum
//checksum=checksum-checksum/checksumRange*checksumRange+minChecksum
set eh[t][i]=ph[i]-ph[i]/r*r+m
set i=pn[i]
exitwhen -1==i
endloop
call q.destroy() //clean original base int
//convert the encoder version** into a string
set ec[ev]=t
set s=es.convertToString(ev)
set h=StringLength(s)
//colorize the string
loop
set h=h-1
set c=SubString(s,h,h+1)
set l=StringCase(c,false)==c
if (c==StringCase(c,true) and l) then
if ("0"==c or 0!=S2I(c)) then
//number
set er[t]=er[t]+NUM_COLOR+c
else
//special
set er[t]=er[t]+SPEC_COLOR+c
endif
elseif (l) then
//lower
set er[t]=er[t]+LOWER_COLOR+c
else
//upper
set er[t]=er[t]+UPPER_COLOR+c
endif
exitwhen 0==h
endloop
return t
debug else
debug if (x<=u) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0,0,60,"ENCODER ERROR: INVALID VALID CODE RANGE")
debug endif
debug if (1>=t) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0,0,60,"ENCODER ERROR: INVALID BASE")
debug endif
debug if (1>=mh) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"ENCODER ERROR: INVALID MAX HASH")
debug endif
debug endif
return 0
endmethod
/*************************************************************************************
*
* toString
* Returns the Encoder as a colorized string in VER_BASE.
*
* returns: string
*
*************************************************************************************/
method toString takes nothing returns string
debug if (er[this] !=null) then
return er[this]
debug endif
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"ENCODER ERROR: INVALID ENCODER")
debug return null
endmethod
/*************************************************************************************
*
* convertSting
* Returns an Encoder by converting an Encoder string into an Encoder.
*
* string s: Encoder id string
*
* returns: Encoder
*
*************************************************************************************/
static method convertString takes string s returns thistype
return ec[es.convertToInteger(s)]
endmethod
/*************************************************************************************
*
* add
* Adds a CodeRange to an Encoder.
*
* CodeRange r: CodeRange to be added
*
* returns: nothing
*
*************************************************************************************/
method add takes CodeRange r returns nothing
debug if (null!=er[this]) then
call Link(this).point(r)
set el[this]=r
debug else
debug if (null==er[this]) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"ENCODER ERROR: INVALID ENCODER")
debug endif
debug endif
endmethod
/*************************************************************************************
*
* read
* Attempts to read values out of a code string.
*
* string s: Code string to read values out of
* integer p: Player id to read code string for
*
* returns: DataBuffer
* 0 means DataBuffer couldn't be openned
*
*************************************************************************************/
method read takes string s,integer p returns DataBuffer
local BigInt i //code integer
local integer k=StringLength(s)
local string c //character
local string b="" //filtered code string
local DataBuffer t //data buffer
local integer v //value
local integer n //node
local integer array o //original values
local boolean f
//first ensure that encoder is valid
if (null!=er[this] and 0!=k) then
//remove all DELIMITERs from code
loop
set k=k-1
set c=SubString(s,k,k+1)
if (c!=DELIMITER) then
set b=c+b
endif
exitwhen 0==k
endloop
set n=StringLength(b)
if (n<ml[this] or n>mx[this]) then
return 0
endif
//convert string into a BigInt
set i=BigInt.convertString(b,eb[this])
if (0==i) then
return 0
endif
//unscramble
call UnapplyScramble(i,p)
//retrieve whether the checksum is location at the
//back or the front
// true: front
// false: back
set f=integer(eh[this])>rsh[el[this]]
if (not f) then
set i.base=b10
endif
//if the stored checksum wasn't equal to the generated checksum, the
// code isn't valid. This is if the checksum is in the back.
//checksum stored in code //code's actual checksum
if (not f and i.divide(eh[this])!=Checksum(i,eh[this])) then
call i.destroy()
return 0
endif
//allocate data buffer
if (0==dn[0]) then
set dc=dc+1
set t=dc
else
set t=dn[0]
set dn[0]=dn[t]
endif
set dn[t]=t
set dl[t]=t
//initialize data buffer
set de[t]=this //data buffer encoder
set dm[t]=Link(this).start() //open the loop
loop
//prepare next slot
call t.open()
//exit when there are no slots left (finalized)
exitwhen df[t]
//divide BigInt by shifted high bound of current node of current slot
// this returns the value*
set v=i.divide(rsh[dd[dl[t]]])
//retrieve node
set n=dd[dl[t]]
set o[dl[t]]=v
//shift the value back to what it originally was
if (rf[n]==AN) then
set v=-v+rh[n]
else
set v=v+rl[n]
endif
//store value into depth pointer id
set dm[t].depthPointer.id=v
//store value
set dv[dl[t]]=v
endloop
//unset finalization flag
set df[t]=false
//if the checksum was in the front
if (f) then
//first, retrieve the checksum
set v=i.toInt()
//rebuild the entire number
call i.destroy()
set i=i.create(b10)
set n=dl[t]
loop
call i.multiply(rsh[dd[n]])
call i.add(o[n],0)
set n=dl[n]
exitwhen n==t
endloop
//compare the checksum stored in the number to the checksum
//that the number actually generates
if (v!=Checksum(i,eh[this])) then
//if they aren't equal, code wasn't valid
call i.destroy()
set de[t]=0 //data buffer encoder=null
//deallocate data buffer
set dn[dl[t]]=dn[0]
set dn[0]=t
//return null
return 0
endif
endif
call i.destroy()
//code was valid, so return DataBuffer
set dp[t]=dn[t] //current node
set dz[t]=di[dn[t]] //current link id
return t
endif
return 0
endmethod
/*************************************************************************************
*
* write
* Attempts to write value into a code string.
*
* integer p: Player id to write code string for
*
* returns: DataBuffer
* 0 means DataBuffer couldn't be openned
*
*************************************************************************************/
method write takes integer p returns DataBuffer
local integer t
//ensure the player is valid
debug if (0!=ph) then
//ensure the encoder is valid
if (null!=er[this]) then
//allocate data buffer
if (0==dn[0]) then
set dc=dc+1
set t=dc
else
set t=dn[0]
set dn[0]=dn[t]
endif
set dn[t]=t
set dl[t]=t
//initialize the buffer
set dp[t]=p //data buffer player
set de[t]=this //data buffer encoder
set df[t]=false //buffer finalized?
set dw[t]=true //open data buffer for writing
set dm[t]=Link(this).start() //open the loop
//open first buffer slot
call DataBuffer(t).open()
//return the buffer
return t
endif
debug else
debug if (0==ph) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"ENCODER ERROR: INVALID PLAYER")
debug endif
debug endif
//returning 0 means something was wrong
return 0
endmethod
endstruct
/*************************************************************************************
*
* Initialization
*
*************************************************************************************/
private module Init
private static method onInit takes nothing returns nothing
//Retrieve all human player hashes (StringHash player name)
local integer i=11
local player p
set pn[16]=-1
loop
set p=Player(i)
if (GetPlayerSlotState(p)==PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p)==MAP_CONTROL_USER) then
set ph[i]=StringHash(StringCase(GetPlayerName(p)+PLAYER_CHECKSUM_SALT,false))
if (0>ph[i]) then
set ph[i]=-ph[i]
endif
set pn[i]=pn[16]
set pn[16]=i
endif
exitwhen 0==i
set i=i-1
endloop
set p=null
//initialize base 10, binary, and the global encoder base
set b10=Base["0123456789"]
set es=Base[VER_BASE]
endmethod
endmodule
private struct Inits extends array
implement Init
endstruct
endlibrary