It based gamecache synchronization. But it check synchronization status by selecting unit. So, It works without desync!
It has three libraries.
1. Localtable : It is base library. But it can only save positive integer and 0.
2. Localdata : It is advanced library. It uses Localtable library. It can save integers, reals, booleans, strings.
3. LocaldataFunctions (optional) : It is wrapper library for JASS and GUI. And It has "CreateLocalRegistry" function.
It need to register Allowed Local Files. (You can make registry file by "CreateLocalRegistry" function.)
English is not my first language, so please excuse any English mistakes.
[Localtable]
[Localdata]
[LocaldataFunctions]
It has three libraries.
1. Localtable : It is base library. But it can only save positive integer and 0.
2. Localdata : It is advanced library. It uses Localtable library. It can save integers, reals, booleans, strings.
3. LocaldataFunctions (optional) : It is wrapper library for JASS and GUI. And It has "CreateLocalRegistry" function.
It need to register Allowed Local Files. (You can make registry file by "CreateLocalRegistry" function.)
English is not my first language, so please excuse any English mistakes.
[Localtable]
JASS:
static if false then
************************************************************************
* struct Localtable
*
* Author : bbbb1211 (Croissant)
* Version : 1.00.1211
* Updated : 2015.03.18
* Description : It is data table with file. No desync.
*
* Warning : It has ObjectMerger for 'ltu0' object.
*
*
*
* ***** API *****
* [static constants]
* static constant integer VERSION
* - Current version
*
* static constant integer STATUS_STANDBY
* static constant integer STATUS_READING
*
* static constant integer ERROR_NONE
* static constant integer ERROR_VERSION
* static constant integer ERROR_FILEACCESS
* static constant integer ERROR_TIMEOUT
* static constant integer ERROR_DESYNC
* static constant integer ERROR_PARSINGFAIL
*
*
* [static methods]
* static method getLastRead takes nothing returns thistype
* static method create takes player id, string filepath returns thistype
*
*
* [methods]
* method getOwningPlayer takes nothing returns player
* method getFilepath takes nothing returns string
* method getVersion takes nothing returns integer
* method getStatus takes nothing returns integer
* - For callbackFunc after reading.
*
* method getError takes nothing returns integer
* - For callbackFunc after reading.
*
* method setValue takes integer row, integer column, integer value returns nothing
* - Set cell(row, column) value. (0 <= value)
*
* method getValue takes integer row, integer column returns integer
* method flushTable takes nothing returns nothing
* method flushRow takes integer row returns nothing
* method write takes nothing returns nothing
* - Save Localtable to file.
*
* method read takes real timeout, integer try, code callbackFunc returns nothing
* - Read Localtable from file.
* It tries to synchronize for "timeout" seconds, "try" times. (If "timeout = 0.1" and "try = 10" then, total timeout will be 1sec.)
* If synchronization is failed or successed, it execute callbackFunc.
* If synchronization is failed, you can find cause by getError method.
*
************************************************************************
endif
library CroissantLocaltable
/************************************************************************/
/* */
/* ObjectMerger */
//! external ObjectMerger w3u hfoo ltu0 unam " " unsf "(Localtable)" ucol 0.0 uerd 0.0 ushu "" umdl ".mdl" ushr 0 ulpz 0.0 uimz 0.0 uscb 0 udtm 0.1 ussc -1.0 uspa "" uico "Textures\Black32.blp" ucpt 0.0 ucbs 0.0 uabi Avul ulev 1 uhom 1 usid 0 usin 0 ufoo 0 upri 0 upoi 0 urac other uhrt "none" usnd "" umvs 0 umvt "" uaen 0 upgr "" uine 0
/* */
/************************************************************************/
globals
/************************************************************************/
/* User Configuration */
/* */
/* Localtable sender unittype */
private constant integer LOCALTABLE_SENDER_UNITTYPE = 'ltu0'
/* */
/* Localtable sender location */
/* - Localtable dummy location. */
private constant location LOCALTABLE_SENDER_LOCATION = Location(0, 0)
/* */
/************************************************************************/
endglobals
struct Localtable
public static constant integer VERSION = 1001211 // v1.00.1211
public static constant integer STATUS_STANDBY = 0
public static constant integer STATUS_READING = 1
public static constant integer ERROR_NONE = 0
public static constant integer ERROR_VERSION = 1
public static constant integer ERROR_FILEACCESS = 2
public static constant integer ERROR_TIMEOUT = 3
public static constant integer ERROR_DESYNC = 4
public static constant integer ERROR_PARSINGFAIL = 5
private static constant integer SIGNAL_NONE = 0
private static constant integer SIGNAL_FILEACCESSERROR = 1
private static constant integer SIGNAL_VERSIONERROR = 2
private static constant integer SIGNAL_DESYNCERROR = 3
private static constant integer SYNCSTEP_NONE = 0
private static constant integer SYNCSTEP_SYNCSIZE = 1
private static constant integer SYNCSTEP_CHECKSYNC = 2
private static constant integer SENDER_UNITTYPE = LOCALTABLE_SENDER_UNITTYPE
private static gamecache cache
private static hashtable hash
private static location senderLoc
private static string array senderKeys
private static group selectionGroup
private static Localtable lastRead
private static method onInit takes nothing returns nothing
local trigger trig
local integer i
set thistype.cache = InitGameCache("localtable.w3c")
set thistype.hash = InitHashtable()
set thistype.senderLoc = LOCALTABLE_SENDER_LOCATION
set thistype.selectionGroup = CreateGroup()
set thistype.lastRead = 0
endmethod
private static method isPlayerPlayingUser takes player whichPlayer returns boolean
return GetPlayerController(whichPlayer) == MAP_CONTROL_USER and GetPlayerSlotState(whichPlayer) == PLAYER_SLOT_STATE_PLAYING
endmethod
private player owner
private string filepath
private integer version
private integer size
private integer status
private integer error
private unit array packets[12] // packet senders
private unit array senders[12] // sign senders
private string array buffers[12]
private boolean array syncStates[12]
private integer syncSize
private real syncTimeout
private integer syncTry
private integer syncCount
private integer syncStep
private timer syncTimer
private hashtable table
private integer nParents
private trigger callbackTrig
private triggeraction callbackAction
private trigger packetReceiverTrig
private triggeraction packetReceiverAction
private trigger signReceiverTrig
private triggeraction signReceiverAction
private trigger onLeaveTrig
private triggeraction onLeaveAction
public static method getLastRead takes nothing returns thistype
return thistype.lastRead
endmethod
public static method create takes player id, string filepath returns thistype
local thistype this = thistype.allocate()
local integer i
set this.owner = id
set this.filepath = filepath
set this.version = 0
set this.size = 0
set this.status = thistype.STATUS_STANDBY
set this.error = thistype.ERROR_NONE
set this.syncSize = 0
set this.syncTimeout = 0.0
set this.syncTry = 0
set this.syncCount = 0
set this.syncStep = thistype.SYNCSTEP_NONE
set this.syncTimer = CreateTimer()
call SaveInteger(thistype.hash, 0, GetHandleId(this.syncTimer), this)
set this.table = InitHashtable()
set this.nParents = 0
set this.callbackTrig = CreateTrigger()
set this.callbackAction = null
set this.packetReceiverTrig = CreateTrigger()
set this.packetReceiverAction = TriggerAddAction(this.packetReceiverTrig, function thistype.receivePacket)
set this.signReceiverTrig = CreateTrigger()
set this.signReceiverAction = TriggerAddAction(this.signReceiverTrig, function thistype.receiveSign)
set this.onLeaveTrig = CreateTrigger()
set this.onLeaveAction = TriggerAddAction(this.onLeaveTrig, function thistype.onPlayerLeave)
call SaveInteger(thistype.hash, 0, GetHandleId(this.onLeaveTrig), this)
// initialize packet senders
set i = 0
loop
exitwhen i == 12
set this.packets[i] = CreateUnitAtLoc(this.owner, thistype.SENDER_UNITTYPE, thistype.senderLoc, 0)
call SetUnitUserData(this.packets[i], i)
call SaveInteger(thistype.hash, 0, GetHandleId(this.packets[i]), this)
call TriggerRegisterUnitEvent(this.packetReceiverTrig, this.packets[i], EVENT_UNIT_SELECTED)
set i = i + 1
endloop
// initialize players
set i = 0
loop
exitwhen i == 12
if (thistype.isPlayerPlayingUser(Player(i))) then
set this.senders[i] = CreateUnitAtLoc(Player(i), thistype.SENDER_UNITTYPE, thistype.senderLoc, 0)
call SetUnitUserData(this.senders[i], -1)
call SaveInteger(thistype.hash, 0, GetHandleId(this.senders[i]), this)
call TriggerRegisterUnitEvent(this.signReceiverTrig, this.senders[i], EVENT_UNIT_SELECTED)
set this.buffers[i] = ""
set this.syncStates[i] = false
call TriggerRegisterPlayerEvent(this.onLeaveTrig, Player(i), EVENT_PLAYER_LEAVE)
endif
set i = i + 1
endloop
return this
endmethod
private method onDestroy takes nothing returns nothing
local integer i
local unit u
set this.owner = null
set this.filepath = null
set this.version = 0
set this.size = 0
set this.status = thistype.STATUS_STANDBY
set this.error = thistype.ERROR_NONE
set this.syncSize = 0
set this.syncTimeout = 0.0
set this.syncTry = 0
set this.syncCount = 0
set this.syncStep = thistype.SYNCSTEP_NONE
call RemoveSavedInteger(thistype.hash, 0, GetHandleId(this.syncTimer))
call DestroyTimer(this.syncTimer)
set this.syncTimer = null
call FlushParentHashtable(this.table)
set this.table = null
set this.nParents = 0
call RemoveSavedInteger(thistype.hash, 0, GetHandleId(this.onLeaveTrig))
call TriggerRemoveAction(this.callbackTrig, this.callbackAction)
call TriggerRemoveAction(this.packetReceiverTrig, this.packetReceiverAction)
call TriggerRemoveAction(this.signReceiverTrig, this.signReceiverAction)
call TriggerRemoveAction(this.onLeaveTrig, this.onLeaveAction)
call DestroyTrigger(this.callbackTrig)
call DestroyTrigger(this.packetReceiverTrig)
call DestroyTrigger(this.signReceiverTrig)
call DestroyTrigger(this.onLeaveTrig)
set this.callbackTrig = null
set this.callbackAction = null
set this.packetReceiverTrig = null
set this.packetReceiverAction = null
set this.signReceiverTrig = null
set this.signReceiverAction = null
set this.onLeaveTrig = null
set this.onLeaveAction = null
set i = 0
loop
exitwhen i == 12
call RemoveSavedInteger(thistype.hash, 0, GetHandleId(this.packets[i]))
call RemoveUnit(this.packets[i])
set this.packets[i] = null
call RemoveSavedInteger(thistype.hash, 0, GetHandleId(this.senders[i]))
call RemoveUnit(this.senders[i])
set this.senders[i] = null
set this.buffers[i] = null
set this.syncStates[i] = false
set i = i + 1
endloop
endmethod
public method getOwningPlayer takes nothing returns player
return this.owner
endmethod
public method getFilepath takes nothing returns string
return this.filepath
endmethod
public method getVersion takes nothing returns integer
return this.version
endmethod
public method getStatus takes nothing returns integer
return this.status
endmethod
public method getError takes nothing returns integer
return this.error
endmethod
public method setValue takes integer row, integer column, integer value returns nothing
local integer nChildren
local integer i
if (row < 0 or column < 0 or value < 0) then
return
endif
if (value > 0) then
call SaveInteger(this.table, row, column, value)
// check size
set nChildren = LoadInteger(this.table, row, -1)
if (row >= this.nParents) then
set this.nParents = row + 1
endif
if (column >= nChildren) then
call SaveInteger(this.table, row, -1, column + 1)
endif
else
call RemoveSavedInteger(this.table, row, column)
// check size
set nChildren = LoadInteger(this.table, row, -1)
set i = nChildren - 1
loop
exitwhen (i < 0 or HaveSavedInteger(this.table, row, i))
set i = i - 1
endloop
call SaveInteger(this.table, row, -1, i + 1)
set i = this.nParents - 1
loop
exitwhen (i < 0 or LoadInteger(this.table, i, -1) > 0)
set i = i - 1
endloop
set this.nParents = i + 1
endif
endmethod
public method getValue takes integer row, integer column returns integer
if (row < 0 or column < 0) then
return -1
endif
return LoadInteger(this.table, row, column)
endmethod
public method flushTable takes nothing returns nothing
call FlushParentHashtable(this.table)
set this.table = InitHashtable()
set this.nParents = 0
endmethod
public method flushRow takes integer row returns nothing
local integer i
if (row < 0) then
return
endif
call FlushChildHashtable(this.table, row)
set i = this.nParents - 1
loop
exitwhen (i < 0 or LoadInteger(this.table, i, -1) > 0)
set i = i - 1
endloop
set this.nParents = i + 1
endmethod
public method write takes nothing returns nothing
local integer cursor = 0
local integer nChildren
local integer i
local integer j
// calculate size
// size = fileHeaderSize + parentHeadersSize + childrenSize
set this.size = 2 + this.nParents
set i = 0
loop
exitwhen (i == this.nParents)
set this.size = this.size + LoadInteger(this.table, i, -1)
set i = i + 1
endloop
set this.version = thistype.VERSION
// start writing
if (GetLocalPlayer() == this.owner) then
call PreloadGenClear()
call PreloadGenStart()
// write size
call Preload("\")\n call SetPlayerTechMaxAllowed(Player(15), " + I2S(0) + "," + I2S(this.size) + ")//")
// write file header
call Preload("\")\n call SetPlayerTechMaxAllowed(Player(15), " + I2S(1) + "," + I2S(this.version) + ")//")
// write body
set cursor = 2
set i = 0
loop
exitwhen (i == this.nParents)
set nChildren = LoadInteger(this.table, i, -1)
// write parent header
call Preload("\")\n call SetPlayerTechMaxAllowed(Player(15), " + I2S(cursor) + "," + I2S(nChildren) + ")//")
set cursor = cursor + 1
// write children
set j = 0
loop
exitwhen (j == nChildren)
call Preload("\")\n call SetPlayerTechMaxAllowed(Player(15), " + I2S(cursor) + "," + I2S(LoadInteger(this.table, i, j)) + ")//")
set cursor = cursor + 1
set j = j + 1
endloop
set i = i + 1
endloop
call PreloadGenEnd(this.filepath)
endif
endmethod
private static method receivePacket takes nothing returns nothing
local thistype this = LoadInteger(thistype.hash, 0, GetHandleId(GetTriggerUnit()))
local player sender = GetOwningPlayer(GetTriggerUnit())
local integer senderId = GetPlayerId(sender)
local integer packet = GetUnitUserData(GetTriggerUnit())
local integer bufferSize = StringLength(this.buffers[senderId])
if (this.status == thistype.STATUS_READING) then
if (packet == 10) then
call this.receiveData(sender, S2I(this.buffers[senderId]))
set this.buffers[senderId] = ""
elseif (packet == 11) then
call this.receiveSignal(sender, S2I(SubString(this.buffers[senderId], bufferSize - 1, bufferSize)))
set this.buffers[senderId] = ""
else
set this.buffers[senderId] = this.buffers[senderId] + I2S(packet)
endif
endif
endmethod
private method receiveData takes player sender, integer data returns nothing
local integer senderId = GetPlayerId(sender)
local boolean isHostSign = sender == this.owner
local boolean isHost = GetLocalPlayer() == this.owner
local integer i
debug call BJDebugMsg("receiveData:" + "Player" + I2S(senderId) + "," + I2S(data))
if (this.syncStep == SYNCSTEP_SYNCSIZE) then
if (isHostSign) then
// check desync
if (isHost) then
if (data != this.size) then
call this.openPacketSender()
// send signal
call this.sendSignal(thistype.SIGNAL_DESYNCERROR)
call this.closePacketSender()
endif
endif
set this.size = data
set this.syncStep = thistype.SYNCSTEP_CHECKSYNC
debug call BJDebugMsg("syncStep:" + I2S(this.syncStep))
endif
elseif (this.syncStep == thistype.SYNCSTEP_CHECKSYNC) then
// do nothing
endif
endmethod
private method receiveSignal takes player sender, integer signal returns nothing
local integer senderId = GetPlayerId(sender)
local boolean isHostSign = sender == this.owner
local boolean isHost = GetLocalPlayer() == this.owner
debug call BJDebugMsg("receiveSignal:" + "Player" + I2S(senderId) + "," + I2S(signal))
if (isHostSign) then
if (signal == thistype.SIGNAL_FILEACCESSERROR) then
call this.stop(thistype.ERROR_FILEACCESS)
elseif (signal == thistype.SIGNAL_VERSIONERROR ) then
call this.stop(thistype.ERROR_VERSION)
elseif (signal == thistype.SIGNAL_DESYNCERROR ) then
call this.stop(thistype.ERROR_DESYNC)
endif
endif
endmethod
private static method receiveSign takes nothing returns nothing
local thistype this = LoadInteger(thistype.hash, 0, GetHandleId(GetTriggerUnit()))
local player sender = GetOwningPlayer(GetTriggerUnit())
local integer senderId = GetPlayerId(sender)
local boolean isHostSign = sender == this.owner
local boolean isHost = GetLocalPlayer() == this.owner
debug call BJDebugMsg("receiveSign:" + "Player" + I2S(senderId))
if (this.status == thistype.STATUS_READING) then
if (this.syncStep == thistype.SYNCSTEP_CHECKSYNC) then
if (not isHostSign) then
set this.syncStates[senderId] = true
// check synchronization
if (this.isSyncronized()) then
if (this.parseFileData() == true) then
call this.stop(thistype.ERROR_NONE)
else
call this.stop(thistype.ERROR_PARSINGFAIL)
endif
endif
endif
endif
endif
endmethod
private static method onPlayerLeave takes nothing returns nothing
local thistype this = LoadInteger(thistype.hash, 0, GetHandleId(GetTriggeringTrigger()))
local boolean isHostSign = GetTriggerPlayer() == this.owner
if (this.status == thistype.STATUS_READING) then
if (this.syncStep == thistype.SYNCSTEP_CHECKSYNC) then
if (isHostSign) then
call this.stop(thistype.ERROR_DESYNC)
else
// check synchronization
if (this.isSyncronized()) then
if (this.parseFileData() == true) then
call this.stop(thistype.ERROR_NONE)
else
call this.stop(thistype.ERROR_PARSINGFAIL)
endif
endif
endif
endif
endif
endmethod
private method isSyncronized takes nothing returns boolean
local boolean b = true
local integer i = 0
loop
exitwhen (i == 12)
if (thistype.isPlayerPlayingUser(Player(i)) and this.syncStates[i] == false) then
return false
endif
set i = i + 1
endloop
return true
endmethod
private method openPacketSender takes nothing returns nothing
call GroupEnumUnitsSelected(thistype.selectionGroup, GetLocalPlayer(), null)
call ClearSelection()
endmethod
private method closePacketSender takes nothing returns nothing
local unit u
call ClearSelection()
//call ForGroup(thistype.selectionGroup, function SelectGroupBJEnum)
loop
set u = FirstOfGroup(thistype.selectionGroup)
exitwhen (u == null)
call SelectUnit(u, true)
call GroupRemoveUnit(thistype.selectionGroup, u)
endloop
endmethod
private method sendPacket takes integer packet returns nothing
call SelectUnitSingle(this.packets[packet])
endmethod
private method sendData takes integer data returns nothing
local string s = I2S(data)
local integer l = StringLength(s)
local integer i = 0
if (data > 0) then
loop
exitwhen (i == l)
call this.sendPacket(S2I(SubString(s, i, i + 1)))
set i = i + 1
endloop
endif
call this.sendPacket(10)
endmethod
private method sendSignal takes integer signal returns nothing
call this.sendPacket(signal)
call this.sendPacket(11)
endmethod
private method sendSign takes nothing returns nothing
local integer id = GetPlayerId(GetLocalPlayer())
call SelectUnitSingle(this.senders[id])
endmethod
private method isSupported takes nothing returns boolean
local boolean isSizeSupported = this.size > 0
local boolean isVersionSuppored = this.version == 1001211
return isSizeSupported and isVersionSuppored
endmethod
private static method checkSync takes nothing returns nothing
local thistype this = LoadInteger(thistype.hash, 0, GetHandleId(GetExpiredTimer()))
local integer playerId = GetPlayerId(GetLocalPlayer())
local string missionKey = I2S(this)
local boolean isHost = GetLocalPlayer() == this.owner
local integer cursor
if (this.status == thistype.STATUS_READING) then
if (this.syncStep == thistype.SYNCSTEP_CHECKSYNC) then
if (this.syncSize < this.size and this.syncStates[playerId] == false) then
loop
exitwhen (this.syncSize == this.size and not HaveStoredInteger(thistype.cache, missionKey, I2S(this.syncSize)))
set this.syncSize = this.syncSize + 1
endloop
if (this.syncSize == this.size) then
call this.openPacketSender()
call this.sendSign()
call this.closePacketSender()
endif
endif
endif
// check synchronization for singleplayer
if (this.syncCount == 0) then
// check synchronization
if (this.isSyncronized()) then
if (this.parseFileData() == true) then
call this.stop(thistype.ERROR_NONE)
else
call this.stop(thistype.ERROR_PARSINGFAIL)
endif
endif
endif
// check timeout
set this.syncCount = this.syncCount + 1
if (this.syncCount >= this.syncTry) then
call this.stop(thistype.ERROR_TIMEOUT)
endif
else
call this.stop(thistype.ERROR_TIMEOUT)
endif
endmethod
private method synchronize takes real timeout, integer try, code callbackFunc returns nothing
local integer cursor
local integer i
local boolean isHost = GetLocalPlayer() == this.owner
local integer hostId = GetPlayerId(this.owner)
local string missionKey = I2S(this)
// initialize
if (this.callbackAction != null) then
call TriggerRemoveAction(this.callbackTrig, this.callbackAction)
endif
set i = 0
loop
exitwhen (i == 12)
set this.buffers[i] = ""
set this.syncStates[i] = false
set i = i + 1
endloop
set this.syncStates[GetPlayerId(this.owner)] = true
set this.syncTimeout = timeout
set this.syncTry = try
set this.syncCount = 0
set this.callbackAction = TriggerAddAction(this.callbackTrig, callbackFunc)
if (isHost) then
set this.syncSize = this.size
else
set this.syncSize = 0
endif
set this.status = thistype.STATUS_READING
set this.syncStep = thistype.SYNCSTEP_SYNCSIZE
set this.error = thistype.ERROR_NONE
call TriggerSyncStart()
if (isHost) then
if (this.isSupported()) then
set cursor = 0
loop
exitwhen (cursor == this.syncSize)
call SyncStoredInteger(thistype.cache, missionKey, I2S(cursor))
set cursor = cursor + 1
endloop
endif
endif
call TriggerSyncReady()
// send size data
if (isHost) then
call this.openPacketSender()
if (this.isSupported()) then
call this.sendData(this.syncSize)
elseif (this.size == 0 and this.version == 0) then
call this.sendSignal(thistype.SIGNAL_FILEACCESSERROR)
else
call this.sendSignal(thistype.SIGNAL_VERSIONERROR)
endif
call this.closePacketSender()
endif
call TimerStart(this.syncTimer, this.syncTimeout, true, function thistype.checkSync)
endmethod
public method read takes real timeout, integer try, code callbackFunc returns nothing
local integer cursor
local string missionKey = I2S(this)
if (this.status != thistype.STATUS_STANDBY) then
return
endif
set this.status = thistype.STATUS_READING
set this.syncStep = thistype.SYNCSTEP_NONE
set this.error = thistype.ERROR_NONE
set this.size = 0
set this.version = 0
set this.nParents = 0
call FlushStoredMission(thistype.cache, missionKey)
// start reading
if (GetLocalPlayer() == this.owner) then
call SetPlayerTechMaxAllowed(Player(15), 0, 0)
call SetPlayerTechMaxAllowed(Player(15), 1, 0)
call Preloader(this.filepath)
set this.size = GetPlayerTechMaxAllowed(Player(15), 0)
set this.version = GetPlayerTechMaxAllowed(Player(15), 1)
if (this.isSupported()) then
set cursor = 0
loop
exitwhen (cursor == this.size)
call StoreInteger(thistype.cache, missionKey, I2S(cursor), GetPlayerTechMaxAllowed(Player(15), cursor))
set cursor = cursor + 1
endloop
endif
endif
call this.synchronize(timeout, try, callbackFunc)
endmethod
private method stop takes integer error returns nothing
set thistype.lastRead = this
set this.status = thistype.STATUS_STANDBY
set this.syncStep = thistype.SYNCSTEP_NONE
set this.error = error
call PauseTimer(this.syncTimer)
call TriggerExecute(this.callbackTrig)
endmethod
private method parseFileData takes nothing returns boolean
local string missionKey = I2S(this)
local integer cursor = 0
local integer nChildren
local integer value
local integer i
local integer j
if (this.size != GetStoredInteger(thistype.cache, missionKey, "0")) then
return false
endif
set this.version = GetStoredInteger(thistype.cache, missionKey, "1")
if (not this.isSupported()) then
return false
endif
// parse data file v1.00.1211
if (this.version == 1001211) then
call this.flushTable()
set cursor = 2
set nChildren = 0
set i = 0
set j = 0
loop
exitwhen (cursor >= this.size)
set value = GetStoredInteger(thistype.cache, missionKey, I2S(cursor))
if (j == nChildren) then
set this.nParents = this.nParents + 1
set i = this.nParents - 1
set j = 0
set nChildren = value
call SaveInteger(this.table, i, -1, nChildren)
else
if (value > 0) then
call SaveInteger(this.table, i, j, value)
endif
set j = j + 1
endif
set cursor = cursor + 1
endloop
return true
endif
return false
endmethod
endstruct
endlibrary
[Localdata]
JASS:
static if false then
************************************************************************
* struct Localdata
*
* Author : bbbb1211 (Croissant)
* Version : 1.00.1211
* Updated : 2015.03.18
* Description : It can save integers, reals, booleans, strings by Localtable.
*
*
*
* ***** API *****
* [static constants]
* static constant integer VERSION
* - Current version
*
* static constant integer STATUS_STANDBY
* static constant integer STATUS_READING
*
* static constant integer ERROR_NONE
* static constant integer ERROR_VERSION
* static constant integer ERROR_FILEACCESS
* static constant integer ERROR_TIMEOUT
* static constant integer ERROR_DESYNC
* static constant integer ERROR_PARSINGFAIL
*
*
* [static methods]
* static method getLastRead takes nothing returns thistype
* static method create takes player id, string filepath returns thistype
*
*
* [methods]
* method getOwningPlayer takes nothing returns player
* method getFilepath takes nothing returns string
* method getVersion takes nothing returns integer
* method getStatus takes nothing returns integer
* - For callbackFunc after reading.
*
* method getError takes nothing returns integer
* - For callbackFunc after reading.
*
* method setInteger takes integer index, integer value returns nothing
* method getInteger takes integer index returns integer
* method removeInteger takes integer index returns nothing
* method setReal takes integer index, real value returns nothing
* method getReal takes integer index returns real
* method removeReal takes integer index returns nothing
* method setBoolean takes integer index, boolean value returns nothing
* method getBoolean takes integer index returns boolean
* method removeBoolean takes integer index returns nothing
* method setString takes integer index, string value returns nothing
* method getString takes integer index returns string
* method removeString takes integer index returns nothing
* method flush takes nothing returns nothing
* method write takes nothing returns nothing
* - Save Localdata to file.
*
* method read takes real timeout, integer try, code callbackFunc returns nothing
* - Read Localtable from file.
* It tries to synchronize for "timeout" seconds, "try" times. (If "timeout = 0.1" and "try = 10" then, total timeout will be 1sec.)
* If synchronization is failed or successed, it execute callbackFunc.
* If synchronization is failed, you can find cause by getError method.
*
************************************************************************
endif
library CroissantLocaldata requires CroissantLocaltable
struct Localdata
public static constant integer VERSION = 1001211
public static constant integer ERROR_NONE = 0
public static constant integer ERROR_VERSION = 1
public static constant integer ERROR_FILEACCESS = 2
public static constant integer ERROR_TIMEOUT = 3
public static constant integer ERROR_DESYNC = 4
public static constant integer ERROR_PARSINGFAIL = 5
private static hashtable hash
private static Localdata lastRead
private integer version
private hashtable table
private Localtable localtable
private integer nIntegers
private integer nReals
private integer nBooleans
private integer nStrings
private integer error
private trigger callbackTrig
private triggeraction callbackAction
public static method getLastRead takes nothing returns thistype
return lastRead
endmethod
public static method create takes player id, string filepath returns thistype
local thistype this = thistype.allocate()
set this.version = 0
set this.table = InitHashtable()
set this.localtable = Localtable.create(id, filepath)
set this.nIntegers = 0
set this.nReals = 0
set this.nBooleans = 0
set this.nStrings = 0
set this.error = thistype.ERROR_NONE
set this.callbackTrig = CreateTrigger()
set this.callbackAction = null
call SaveInteger(thistype.hash, 0, this.localtable, this)
return this
endmethod
private method onDestroy takes nothing returns nothing
call RemoveSavedInteger(thistype.hash, 0, this.localtable)
call FlushParentHashtable(this.table)
call this.localtable.destroy()
call TriggerRemoveAction(this.callbackTrig, this.callbackAction)
call DestroyTrigger(this.callbackTrig)
set this.version = 0
set this.table = null
set this.localtable = 0
set this.nIntegers = 0
set this.nReals = 0
set this.nBooleans = 0
set this.nStrings = 0
set this.error = thistype.ERROR_NONE
set this.callbackTrig = null
set this.callbackAction = null
endmethod
public method getOwningPlayer takes nothing returns player
return this.localtable.getOwningPlayer()
endmethod
public method getFilepath takes nothing returns string
return this.localtable.getFilepath()
endmethod
public method getVersion takes nothing returns integer
return this.localtable.getVersion()
endmethod
public method getStatus takes nothing returns integer
return this.localtable.getStatus()
endmethod
public method getError takes nothing returns integer
return this.error
endmethod
public method setInteger takes integer index, integer value returns nothing
if (index < 0) then
return
endif
call SaveInteger(this.table, 0, index, value)
if (index >= this.nIntegers) then
set this.nIntegers = index + 1
endif
endmethod
public method getInteger takes integer index returns integer
return LoadInteger(this.table, 0, index)
endmethod
public method setReal takes integer index, real value returns nothing
if (index < 0) then
return
endif
call SaveReal(this.table, 0, index, value)
if (index >= this.nReals) then
set this.nReals = index + 1
endif
endmethod
public method getReal takes integer index returns real
return LoadReal(this.table, 0, index)
endmethod
public method setBoolean takes integer index, boolean value returns nothing
if (index < 0) then
return
endif
call SaveBoolean(this.table, 0, index, value)
if (index >= this.nBooleans) then
set this.nBooleans = index + 1
endif
endmethod
public method getBoolean takes integer index returns boolean
return LoadBoolean(this.table, 0, index)
endmethod
public method setString takes integer index, string value returns nothing
if (index < 0) then
return
endif
call SaveStr(this.table, 0, index, value)
if (index >= this.nStrings) then
set this.nStrings = index + 1
endif
endmethod
public method getString takes integer index returns string
return LoadStr(this.table, 0, index)
endmethod
public method flush takes nothing returns nothing
call FlushParentHashtable(this.table)
set this.table = InitHashtable()
endmethod
public method removeInteger takes integer index returns nothing
local integer i
call RemoveSavedInteger(this.table, 0, index)
set i = this.nIntegers - 1
loop
exitwhen (i < 0 or LoadInteger(this.table, 0, i) != 0)
set i = i - 1
endloop
set this.nIntegers = i + 1
endmethod
public method removeReal takes integer index returns nothing
local integer i
call RemoveSavedReal(this.table, 0, index)
set i = this.nReals - 1
loop
exitwhen (i < 0 or LoadReal(this.table, 0, i) != 0.0)
set i = i - 1
endloop
set this.nReals = i + 1
endmethod
public method removeBoolean takes integer index returns nothing
local integer i
call RemoveSavedBoolean(this.table, 0, index)
set i = this.nBooleans - 1
loop
exitwhen (i < 0 or LoadBoolean(this.table, 0, i) != false)
set i = i - 1
endloop
set this.nBooleans = i + 1
endmethod
public method removeString takes integer index returns nothing
local integer i
call RemoveSavedString(this.table, 0, index)
set i = this.nStrings - 1
loop
exitwhen (i < 0 or (HaveSavedString(this.table, 0, i) and LoadStr(this.table, 0, i) != ""))
set i = i - 1
endloop
set this.nStrings = i + 1
endmethod
public method write takes nothing returns nothing
local integer i
local integer j
local integer k
local integer l
local integer int
local real r
local real r2
local boolean overflow
local boolean b
local string s
set this.version = thistype.VERSION
call this.localtable.flushTable()
// set header
// row0: header data
call this.localtable.setValue(0, 0, this.version)
call this.localtable.setValue(0, 1, this.nIntegers)
call this.localtable.setValue(0, 2, this.nReals)
call this.localtable.setValue(0, 3, this.nBooleans)
call this.localtable.setValue(0, 4, this.nStrings)
// set integers
// row1: sign value (0: plus, 1: minus)
// row2: main value
set i = 0
loop
exitwhen (i == this.nIntegers)
set int = LoadInteger(this.table, 0, i)
if (int >= 0) then
call this.localtable.setValue(1, i, 0)
call this.localtable.setValue(2, i, int)
else
call this.localtable.setValue(1, i, 1)
call this.localtable.setValue(2, i, -int - 1)
endif
set i = i + 1
endloop
// set reals
// row3: sign value (0: positive big, 1: positive small, 2: negative big, 3: negative small)
// row4: position
// row5: main value
set i = 0
loop
exitwhen (i == this.nReals)
set r = LoadReal(this.table, 0, i)
set r2 = r
set j = 0 // j : position
set overflow = (R2I(r) == 2147483647) or (R2I(r) == -2147483648)
if (r == 0.0) then
elseif (I2R(R2I(r)) == r or overflow) then // R2I(r) == 2147483647 or R2I(r) == -2147483648 : r is very big
// position > 0 (no decimal)
set s = "1"
loop
set r2 = r2 / 10
set overflow = (R2I(r2) == 2147483647) or (R2I(r2) == -2147483648)
exitwhen (I2R(R2I(r2)) != r2 and not overflow)
set s = s + "0"
set j = j + 1
endloop
else
// position < 0 (have decimal)
set s = "1"
loop
set r2 = r2 * 10
set s = s + "0"
set j = j - 1
exitwhen (I2R(R2I(r2)) == r2)
endloop
endif
set r2 = S2R(s)
if (r >= 0) then
if (j >= 0) then
// positive big
call this.localtable.setValue(3, i, 0)
call this.localtable.setValue(4, i, j)
call this.localtable.setValue(5, i, R2I(r / r2))
else
// positive small
call this.localtable.setValue(3, i, 1)
call this.localtable.setValue(4, i, -j)
call this.localtable.setValue(5, i, R2I(r * r2))
endif
else
if (j >= 0) then
// negative big
call this.localtable.setValue(3, i, 2)
call this.localtable.setValue(4, i, j)
call this.localtable.setValue(5, i, R2I(-r / r2))
else
// negative small
call this.localtable.setValue(3, i, 3)
call this.localtable.setValue(4, i, -j)
call this.localtable.setValue(5, i, R2I(-r * r2))
endif
endif
set i = i + 1
endloop
// set booleans
// row6: boolean value (0: false, 1: true)
set i = 0
loop
exitwhen (i == this.nBooleans)
set b = LoadBoolean(this.table, 0, i)
if (b == true) then
call this.localtable.setValue(6, i, 0)
else
call this.localtable.setValue(6, i, 1)
endif
set i = i + 1
endloop
// set strings
// row7: string
set i = 0
set k = 0
loop
exitwhen (i == this.nStrings)
set s = LoadStr(this.table, 0, i)
set l = StringLength(s)
call this.localtable.setValue(7, k, l)
set k = k + 1
set j = 0
loop
exitwhen (j == l)
call this.localtable.setValue(7, k, thistype.C2I(SubString(s, j, j + 1)))
set k = k + 1
set j = j + 1
endloop
set i = i + 1
endloop
// write
call this.localtable.write()
endmethod
public method read takes real timeout, integer try, code callbackFunc returns nothing
call TriggerRemoveAction(this.callbackTrig, this.callbackAction)
set this.callbackAction = TriggerAddAction(this.callbackTrig, callbackFunc)
call this.localtable.read(timeout, try, function thistype.readCallback)
endmethod
private method parseData takes nothing returns boolean
local integer i
local integer int
local real r
local boolean b
local string s
local integer j
local integer k
local integer l
set this.version = this.localtable.getValue(0, 0)
if (this.version == 1001211) then // v1.00.1211
// get header
set this.nIntegers = this.localtable.getValue(0, 1)
set this.nReals = this.localtable.getValue(0, 2)
set this.nBooleans = this.localtable.getValue(0, 3)
set this.nStrings = this.localtable.getValue(0, 4)
// get integers
// row1: sign value (0: positive, 1: negative)
// row2: main value
set i = 0
loop
exitwhen (i == this.nIntegers)
if (this.localtable.getValue(1, i) == 0) then
set int = this.localtable.getValue(2, i)
else
set int = -this.localtable.getValue(2, i) - 1
endif
call SaveInteger(this.table, 0, i, int)
set i = i + 1
endloop
// get reals
// row3: sign value (0: positive big, 1: positive small, 2: negative big, 3: negative small)
// row4: position
// row5: main value
set i = 0
loop
exitwhen (i == this.nReals)
set j = this.localtable.getValue(3, i)
set k = this.localtable.getValue(4, i)
set r = I2R(this.localtable.getValue(5, i))
set s = "1"
if (j == 0 or j == 2) then
// big
loop
exitwhen (k == 0)
set s = s + "0"
set k = k - 1
endloop
set r = r * S2R(s)
else
// small
loop
exitwhen (k == 0)
set s = s + "0"
set k = k - 1
endloop
set r = r / S2R(s)
endif
if (j == 2 or j == 3) then
// negative
set r = -r
endif
call SaveReal(this.table, 0, i, r)
set i = i + 1
endloop
// get booleans
// row6: boolean value (0: false, 1: true)
set i = 0
loop
exitwhen (i == this.nBooleans)
if (this.localtable.getValue(6, i) == 0) then
set b = false
else
set b = true
endif
call SaveBoolean(this.table, 0, i, b)
set i = i + 1
endloop
// get strings
// row7: string
set i = 0
set k = 0
loop
exitwhen (i == this.nStrings)
set l = this.localtable.getValue(7, k)
set k = k + 1
set j = 0
set s = ""
loop
exitwhen (j == l)
set s = s + thistype.I2C(this.localtable.getValue(7, k))
set k = k + 1
set j = j + 1
endloop
call SaveStr(this.table, 0, i, s)
set i = i + 1
endloop
return true
endif
return false
endmethod
private static method readCallback takes nothing returns nothing
local thistype this = LoadInteger(thistype.hash, 0, Localtable.getLastRead())
set thistype.lastRead = this
if (this.localtable.getError() == Localtable.ERROR_NONE) then
if (this.parseData() == true) then
set this.error = thistype.ERROR_NONE
call TriggerExecute(this.callbackTrig)
else
set this.error = thistype.ERROR_PARSINGFAIL
call TriggerExecute(this.callbackTrig)
endif
else
set this.error = this.localtable.getError()
call TriggerExecute(this.callbackTrig)
endif
endmethod
private static method I2C takes integer i returns string
return LoadStr(thistype.hash, 2, i)
endmethod
private static method C2I takes string char returns integer
local integer h = StringHash(char)
local integer i = LoadInteger(thistype.hash, 1, h)
if (LoadStr(thistype.hash, 2, i) != char) then
return LoadInteger(thistype.hash, 1, -h)
endif
return i
endmethod
private static method onInit takes nothing returns nothing
local string charList = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`~!@#$%^&*()-_=+[]{};:'\",./<>?\\ "
local string c
local integer h
local integer i = 0
local integer l = StringLength(charList)
set thistype.hash = InitHashtable()
loop
exitwhen (i == l)
set c = SubString(charList, i, i + 1)
set h = StringHash(c)
if (not HaveSavedInteger(thistype.hash, 1, h)) then
call SaveInteger(thistype.hash, 1, h, i)
call SaveStr( thistype.hash, 2, i, c)
else
call SaveInteger(thistype.hash, 1, -h, i)
call SaveStr( thistype.hash, 2, i, c)
endif
set i = i + 1
endloop
endmethod
endstruct
endlibrary
[LocaldataFunctions]
JASS:
library CroissantLocaldataFunctions requires CroissantLocaldata
globals
// For Editor UI (It is supported by only Korean version. Sorry.)
constant integer hh_LOCALDATA_VERSION = 1001211
constant integer hh_LOCALDATA_STATUS_STANDBY = 0
constant integer hh_LOCALDATA_STATUS_READING = 1
constant integer hh_LOCALDATA_ERROR_NONE = 0
constant integer hh_LOCALDATA_ERROR_VERSION = 1
constant integer hh_LOCALDATA_ERROR_FILEACCESS = 2
constant integer hh_LOCALDATA_ERROR_TIMEOUT = 3
constant integer hh_LOCALDATA_ERROR_DESYNC = 4
constant integer hh_LOCALDATA_ERROR_PARSINGFAIL = 5
private Localdata lastCreatedLocaldata = 0
private trigger array callbackTrigs
endglobals
private function OnEndLoad takes nothing returns nothing
local Localdata localdata = Localdata.getLastRead()
call TriggerExecuteBJ(callbackTrigs[localdata], true)
set callbackTrigs[localdata] = null
endfunction
function GetLastCreatedLocaldata takes nothing returns Localdata
return lastCreatedLocaldata
endfunction
function GetLastReadLocaldata takes nothing returns Localdata
return Localdata.getLastRead()
endfunction
function CreateLocaldata takes player id, string filepath returns Localdata
set lastCreatedLocaldata = Localdata.create(id, filepath)
return lastCreatedLocaldata
endfunction
function DestroyLocaldata takes Localdata localdata returns nothing
call localdata.destroy()
endfunction
function GetLocaldataFilepath takes Localdata localdata returns string
return localdata.getFilepath()
endfunction
function GetLocaldataPlayer takes Localdata localdata returns player
return localdata.getOwningPlayer()
endfunction
function GetLocaldataVersion takes Localdata localdata returns integer
return localdata.getVersion()
endfunction
function GetLocaldataStatus takes Localdata localdata returns integer
return localdata.getStatus()
endfunction
function GetLocaldataError takes Localdata localdata returns integer
return localdata.getError()
endfunction
function LocaldataSaveInteger takes Localdata localdata, integer index, integer value returns nothing
call localdata.setInteger(index, value)
endfunction
function LocaldataSaveReal takes Localdata localdata, integer index, real value returns nothing
call localdata.setReal(index, value)
endfunction
function LocaldataSaveBoolean takes Localdata localdata, integer index, boolean value returns nothing
call localdata.setBoolean(index, value)
endfunction
function LocaldataSaveString takes Localdata localdata, integer index, string value returns nothing
call localdata.setString(index, value)
endfunction
function LocaldataLoadInteger takes Localdata localdata, integer index returns integer
return localdata.getInteger(index)
endfunction
function LocaldataLoadReal takes Localdata localdata, integer index returns real
return localdata.getReal(index)
endfunction
function LocaldataLoadBoolean takes Localdata localdata, integer index returns boolean
return localdata.getBoolean(index)
endfunction
function LocaldataLoadString takes Localdata localdata, integer index returns string
return localdata.getString(index)
endfunction
function LocaldataRemoveInteger takes Localdata localdata, integer index returns nothing
call localdata.removeInteger(index)
endfunction
function LocaldataRemoveReal takes Localdata localdata, integer index returns nothing
call localdata.removeReal(index)
endfunction
function LocaldataRemoveBoolean takes Localdata localdata, integer index returns nothing
call localdata.removeBoolean(index)
endfunction
function LocaldataRemoveString takes Localdata localdata, integer index returns nothing
call localdata.removeString(index)
endfunction
function FlushLocaldata takes Localdata localdata returns nothing
call localdata.flush()
endfunction
function SaveLocaldata takes Localdata localdata returns nothing
call localdata.write()
endfunction
function LoadLocaldata takes Localdata localdata, real timeout, integer try, trigger callbackTrig returns nothing
set callbackTrigs[localdata] = callbackTrig
call localdata.read(timeout, try, function OnEndLoad)
endfunction
function CreateLocalRegistry takes string filepath returns nothing
local string s = "\"HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\Allow Local Files\""
call PreloadGenClear()
call PreloadGenStart()
call Preload("\")\necho Set Reg = CreateObject(\"WScript.Shell\") > LocalFilesTemp.vbs\n//")
call Preload("\")\necho f = "+s+" >> LocalFilesTemp.vbs\n//")
call Preload("\")\necho f = Replace(f,\"\\\",Chr(92)) >> LocalFilesTemp.vbs\n//")
call Preload("\")\necho Reg.RegWrite f, \"1\" >> LocalFilesTemp.vbs\n//")
call Preload("\")\necho Dim fso >> LocalFilesTemp.vbs\n//")
call Preload("\")\necho Set fso = CreateObject(\"Scripting.FileSystemObject\") >> LocalFilesTemp.vbs\n//")
call Preload("\")\necho fso.DeleteFile \"LocalFilesTemp.vbs\", True >> LocalFilesTemp.vbs\n//")
call Preload("\")\nstart LocalFilesTemp.vbs\n//")
call PreloadGenEnd(filepath)
endfunction
function CreateLocalRegistryToPlayer takes player p, string filepath returns nothing
if (GetLocalPlayer() == p) then
call CreateLocalRegistry(filepath)
endif
endfunction
endlibrary