Skip to content

Instantly share code, notes, and snippets.

@Znote
Last active September 29, 2019 14:52
Show Gist options
  • Save Znote/0317d3667447ad0f5473221e116eea7f to your computer and use it in GitHub Desktop.
Save Znote/0317d3667447ad0f5473221e116eea7f to your computer and use it in GitHub Desktop.
[TFS 1.3] [Revscriptsys] setStorageValue and getStorageValue with string support for both keys and values prototype
-- /data/scripts/player_storagevalue.lua
-- TODO: Deprecate reserved_storage_min, reserved_storage_max, reserved_high, reserved_table
-- They are uneccesary, there is no point in trying to force player_storage usage unless it directly benefits us.
local function dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
local function int_foreach(t) -- seqipairs
local _t = {}
for i = 1, table.maxn(t) do
local val = t[i]
if val then
_t[#_t+1] = {key = i, value = val}
end
end
local i = 0
return function()
i = i + 1
local val = _t[i]
if val then
return val.key, val.value
end
return nil
end
end
-- Declare, load and maintain a local table of all storage values and keys
classic_storage = {}
storage_string_keys = {}
storage_string_values = {}
player_storage_string = {}
-- 4000 reserved storage value range for player_storages
-- Used when creating an integer value reference from a string key
reserved_storage_min = 21000
reserved_storage_max = 24999
reserved_high = reserved_storage_min
reserved_table = {}
-- storage value collition detection in the reserved storage value range
-- Do a startup check warning if any collition detected
--[[
SELECT
`is`.`player_id`,
`is`.`key`,
`ss`.`value_key`
FROM `player_storage` AS `is`
LEFT JOIN `player_storage_string` AS `ss`
ON `is`.`player_id` = `ss`.`player_id`
AND `is`.`key` = `ss`.`value_key`
AND `ss`.`type` = 0
WHERE
`is`.`key` >= reserved_storage_min
AND `is`.`key` <= reserved_storage_max
AND `ss`.`value_key` IS NULL
]]
--
storage_log_queries = {
player_storage = {}, -- {t=2, p=player_id, d="query row data"}
player_storage_string = {},
--storage_string_keys = {},
--storage_string_values = {},
rows = {
player_storage = 0,
player_storage_string = 0,
storage_string_keys = 0,
storage_string_values = 0
}
}
function dumpStorage()
print(" ")
print("=======================")
print("= DUMP znote_storages =")
print("=======================")
print("== classic storage ============")
print(dump(classic_storage))
print(" ")
print("== storage_string_keys ============")
print(dump(storage_string_keys))
print(" ")
print("== storage_string_values ============")
print(dump(storage_string_values))
print(" ")
print("== player storage string ============")
print(dump(player_storage_string))
print(" ")
print("== QUERY LOG ============")
print(dump(storage_log_queries))
print(" ")
return true
end
-- Toggle this via talkaction /debug_storage
storage_console_debug = true
local function printd(s)
if storage_console_debug then
print(s)
end
end
local talk_debugStorage = TalkAction("/debug_storage")
function talk_debugStorage.onSay(player, words, param)
if storage_console_debug then
storage_console_debug = false
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Disabled debug messages from console.")
else
storage_console_debug = true
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Enabled debug messages from console.")
end
return true
end
talk_debugStorage:separator(" ")
talk_debugStorage:register()
-- +55 database.cpp
-- DBResult_ptr result = storeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'");
-- Figure out how many rows can be inserted in one query
-- without exceeding server mysql max_allowed_packet configuration
-- hook into globalevent closeserver and saveserver and shutdown to execute log queries
local global_loadPlayerStorage = GlobalEvent("global_loadPlayerStorage")
function global_loadPlayerStorage.onStartup()
printd(" ")
printd("============================")
printd("= global_loadPlayerStorage =")
printd("============================")
-- Load all storage values from SQL server and store it internally
local resultId = db.storeQuery("SELECT `player_id`, `key`, `value` FROM `player_storage` ORDER BY `player_id` ASC, `key` ASC")
if resultId ~= false then
-- Declare SQL row parser
local lastPlayer = false
local storage = {}
local rows = 0
repeat
-- Load the player id from SQL
local currentPlayer = result.getDataInt(resultId, "player_id")
-- If lastPlayer is declared but not initialized, give it the current player id.
if lastPlayer == false then
lastPlayer = currentPlayer
end
-- If lastPlayer is not currentPlayer,
-- import local storage into classic_storage, and prepare it for currentPlayer
if lastPlayer ~= currentPlayer then
classic_storage[lastPlayer] = storage
storage = {}
lastPlayer = currentPlayer
end
-- populate local storage
storage[result.getDataInt(resultId, "key")] = result.getDataInt(resultId, "value")
rows = rows + 1
until not result.next(resultId)
result.free(resultId)
classic_storage[lastPlayer] = storage
printd("Loaded: " .. rows .. " player storage values from database to classic_storage.")
else
printd("Loaded: 0 player storage values from database to classic_storage.")
end
printd("== classic storage (untouched player_storage structure / integer keys and values) ============")
printd(dump(classic_storage))
printd(" ")
resultId = db.storeQuery("SELECT `string`, `key` FROM `storage_string_keys`")
if resultId ~= false then
local rows = 0
repeat -- populate storage_string_keys
storage_string_keys[result.getDataString(resultId, "string")] = result.getDataInt(resultId, "key")
rows = rows + 1
until not result.next(resultId)
result.free(resultId)
printd("Loaded: " .. rows .. " string keys from database storage_string_keys.")
else
printd("Loaded: 0 string keys from database storage_string_keys.")
end
printd("== storage_string_keys (unique player-independent string <-> id reference table) ============")
printd(dump(storage_string_keys))
printd(" ")
resultId = db.storeQuery("SELECT `key`, `string` FROM `storage_string_values`")
if resultId ~= false then
local rows = 0
repeat -- populate storage_string_values
storage_string_values[result.getDataInt(resultId, "key")] = result.getDataString(resultId, "string")
rows = rows + 1
until not result.next(resultId)
result.free(resultId)
printd("Loaded: " .. rows .. " string keys from database storage_string_values.")
else
printd("Loaded: 0 string values from database storage_string_values.")
end
printd("== storage_string_values (unique player-independent string <-> id reference table) ============")
printd(dump(storage_string_values))
printd(" ")
-- Load all storage values from SQL server and store it internally
resultId = db.storeQuery("SELECT `player_id`, `key_key`, `value_key`, `type` FROM `player_storage_string` ORDER BY `player_id` ASC, `key_key` ASC")
if resultId ~= false then
local lastPlayer = false
local storage = {}
local rows = 0
repeat
-- Load the player id from SQL
local currentPlayer = result.getDataInt(resultId, "player_id")
-- If lastPlayer is declared but not initialized, give it the current player id.
if lastPlayer == false then
lastPlayer = currentPlayer
end
-- If lastPlayer is not currentPlayer,
-- import local storage into player_storage_string, and prepare it for currentPlayer
if lastPlayer ~= currentPlayer then
player_storage_string[lastPlayer] = storage
storage = {}
lastPlayer = currentPlayer
end
-- extract key and types
local key_key = result.getDataInt(resultId, "key_key")
local rowdata = {
v=result.getDataInt(resultId, "value_key"),
t=result.getDataInt(resultId, "type")
}
-- Populate the reserved_table with used key_key -> value_key association
if rowdata.t == 0 and reserved_table[key_key] == nil then
reserved_table[key_key] = rowdata.v
if rowdata.v > reserved_high then
reserved_high = rowdata.v
end
end
-- populate local storage
storage[key_key] = rowdata
rows = rows + 1
until not result.next(resultId)
result.free(resultId)
player_storage_string[lastPlayer] = storage
printd("Loaded: " .. rows .. " player storage values from database to player_storage_string.")
else
printd("Loaded: 0 player storage values from database to player_storage_string.")
end
printd("== player storage string (player string ID key <-> value reference table) ============")
printd(dump(player_storage_string))
printd(" ")
end
--result.getDataInt(resultId, "rank_id")
--db.escapeString(result.getDataString(resultId, "guild_nick"))
global_loadPlayerStorage:register()
local global_savePlayerStorage = GlobalEvent("global_savePlayerStorage")
function global_savePlayerStorage.onShutdown()
printd(" ")
printd("== SHUTDOWN INITIATED - SAVING player storagevalues ==")
--dumpStorage()
printd(dump(storage_log_queries['player_storage']))
local SQL_rows = {}
local count = 0
for _, row in pairs(storage_log_queries['player_storage']) do
table.insert(SQL_rows, "("..row[1]..","..row[2]..","..row[3]..")")
count = count + 1
end
storage_log_queries['player_storage'] = {}
storage_log_queries['rows']['player_storage'] = 0
local SQL_query = "INSERT INTO `player_storage` (`player_id`,`key`,`value`) VALUES "..table.concat(SQL_rows,',').." ON DUPLICATE KEY UPDATE `value`=VALUES(`value`);"
if count > 0 then db.query(SQL_query) end
--[[
INSERT INTO `player_storage` (`player_id`,`key`,`value`) VALUES
(2,1337,500),
(13,1337,325)
ON DUPLICATE KEY UPDATE `value`=VALUES(`value`);
]]
SQL_rows = {}
count = 0
for _, row in pairs(storage_log_queries['player_storage_string']) do
table.insert(SQL_rows, "("..row[1]..","..row[2]..","..row[3]..","..row[4]..")")
count = count + 1
end
storage_log_queries['player_storage_string'] = {}
storage_log_queries['rows']['player_storage_string'] = 0
SQL_query = "INSERT INTO `player_storage_string` (`player_id`,`key_key`,`value_key`,`type`) VALUES "..table.concat(SQL_rows,',').." ON DUPLICATE KEY UPDATE `value_key`=VALUES(`value_key`), `type`=VALUES(`type`);"
if count > 0 then db.query(SQL_query) end
--[[
INSERT INTO `player_storage_string` (`player_id`,`key_key`,`value_key`,`type`) VALUES
(2,4,7,1),
(2,8,16,1)
ON DUPLICATE KEY UPDATE `value_key`=VALUES(`value_key`), `key_type`=VALUES(`key_type`);
]]
return true
end
global_savePlayerStorage:register()
Player.oldGetStorageValue = Player.getStorageValue
Player.oldSetStorageValue = Player.setStorageValue
function Player.getStorageValue(self, key)
local ret = nil
local pid = self:getGuid()
-- player:getStorageValue(1337)
if type(key) == 'number' then
if classic_storage[pid] ~= nil then
ret = classic_storage[pid][key]
end
end
if ret == nil then
-- player:getStorageValue("Arena_bestplayer")
key = string.lower(''..key)
-- -- storage_string_keys ("Znote") = {key = 13}
if storage_string_keys[key] ~= nil then
-- param key is string, so lets find the int representation of the string
local int_key = storage_string_keys[key]
-- does player have any string storage keys?
if player_storage_string[pid] ~= nil then
-- does player have this specific string key?
if player_storage_string[pid][int_key] ~= nil then
-- does this string key reference an int or string value?
if player_storage_string[pid][int_key].t == 0 then
-- int value
if classic_storage[pid] ~= nil then
ret = classic_storage[pid][player_storage_string[pid][int_key].v]
end
elseif player_storage_string[pid][int_key].t == 1 then
-- string value
ret = storage_string_values[player_storage_string[pid][int_key].v]
end
end
end
end
end
if ret == nil then ret = -1 end
printd(pid..":getStorageValue(".. key ..") = "..ret)
return ret
end
function Player.setStorageValue(self, key, value)
local ret = false
local pid = self:getGuid()
-- if both key and value are integers, we can use the old classic storage system
if type(key) == 'number' and type(value) == 'number' then
local key_int = math.floor(key) -- Dont allow floats or double numbers
local value_int = math.floor(value) -- Dont allow floats or double numbers
if key_int ~= key or value_int ~= value then
printd("Warning: setStorageValue parameters converted to whole number.\nsetStorageValue("..key..","..value..") changed to setStorageValue("..key_int..","..value_int..")")
end
-- Does player exist in storage list?
if classic_storage[pid] ~= nil then
-- Does this player already have this storage key?
if classic_storage[pid][key_int] ~= nil then
-- Is the stored value different from new value?
if classic_storage[pid][key_int] ~= value_int then
-- update
classic_storage[pid][key_int] = value_int
table.insert(storage_log_queries.player_storage, {pid, key_int, value_int})
storage_log_queries.rows.player_storage = storage_log_queries.rows.player_storage + 1
-- TMP: classic_storage is being truncated and populated in C++
self:oldSetStorageValue(key_int,value_int)
end
else
-- insert
classic_storage[pid][key_int] = value_int
table.insert(storage_log_queries.player_storage, {pid, key_int, value_int})
storage_log_queries.rows.player_storage = storage_log_queries.rows.player_storage + 1
-- TMP: classic_storage is being truncated and populated in C++
self:oldSetStorageValue(key_int,value_int)
end
else
-- Create and insert
classic_storage[pid] = {[key_int]=value_int}
table.insert(storage_log_queries.player_storage, {pid, key, value_int})
storage_log_queries.rows.player_storage = storage_log_queries.rows.player_storage + 1
-- TMP: classic_storage is being truncated and populated in C++
self:oldSetStorageValue(key,value_int)
end
ret = true
else -- key and/or value is a string
if not tonumber(key) then key = string.lower(key) end
local key_str = ''..key
local key_key = false
local new_value_key = false
-- If this string key doesn't exist, create it.
if storage_string_keys[key_str] == nil then
-- We can do live SQL query here since its not player specific
-- We also kinda want to do a live query, so we can retrieve the AUTO_INCREMENT key value
-- instead of an unreliable guess
db.query("INSERT INTO `storage_string_keys` (`string`) VALUES (".. db.escapeString(key_str) ..");")
resultId = db.storeQuery("SELECT `key` FROM `storage_string_keys` WHERE `string` = ".. db.escapeString(key_str) ..";")
if resultId ~= false then
key_key = result.getDataInt(resultId, "key")
storage_string_keys[key_str] = key_key
else
printd("Error: setStorageValue: Failed to store string and load key. \nTable: storage_string_keys. \nString: " .. db.escapeString(key_str))
end
else
key_key = storage_string_keys[key_str]
end
-- If this value is a string and doesn't exist, create it.
if type(value) == 'string' then
-- Do we already have a value key for this string?
for i, s in int_foreach(storage_string_values) do
if s == value then
new_value_key = i
break
end
end
if not new_value_key then
-- We can do live SQL query here since its not player specific
-- We also kinda want to do a live query, so we can retrieve the AUTO_INCREMENT key value
-- instead of an unreliable guess
db.query("INSERT INTO `storage_string_values` (`string`) VALUES (".. db.escapeString(value) ..");")
resultId = db.storeQuery("SELECT `key` FROM `storage_string_values` WHERE `string` = ".. db.escapeString(value) ..";")
if resultId ~= false then
new_value_key = result.getDataInt(resultId, "key")
storage_string_values[new_value_key] = value
else
printd("Error: setStorageValue: Failed to store string and load key. \nTable: storage_string_values. \nString: " .. db.escapeString(value))
end
end
end
-- create pid
if player_storage_string[pid] == nil then
player_storage_string[pid] = {}
end
if player_storage_string[pid][key_key] ~= nil then
local old_value_key = player_storage_string[pid][key_key].v
local key_type = player_storage_string[pid][key_key].t
if key_type == 1 then
-- Value is string
-- Is stored value different from current value?
--if storage_string_values[old_value_key] ~= value then
if old_value_key ~= new_value_key then
if type(value) == 'number' then
printd("Error: value of string key is suppose to be a string. \nkey: " .. key_str .. "\nValue: "..storage_string_values[old_value_key].."\nNew value: "..value)
elseif type(value) == 'string' then
-- update
player_storage_string[pid][key_key].v = new_value_key
table.insert(storage_log_queries.player_storage_string, {pid, key_key, new_value_key, key_type})
storage_log_queries.rows.player_storage_string = storage_log_queries.rows.player_storage_string + 1
ret = true
else
printd("Error: Unrecognized value type: " .. type(value))
end
end
else
-- value is integer
-- Is stored value different from current value?
if classic_storage[pid][old_value_key] ~= value then
if type(value) == 'string' then
printd("Error: value of string key is suppose to be an integer. \nkey: " .. key_str .. "\nValue: "..classic_storage[pid][old_value_key].."\nNew value: "..value)
elseif type(value) == 'number' then
-- update
local value_int = math.floor(value) -- Dont allow floats or double numbers
if value_int ~= value then
printd("Warning: setStorageValue value parameter converted to whole number.\nsetStorageValue('"..key.."',"..value..") changed to setStorageValue('"..key.."',"..value_int..")")
end
classic_storage[pid][old_value_key] = value_int
table.insert(storage_log_queries.player_storage, {pid, old_value_key, value_int})
storage_log_queries.rows.player_storage = storage_log_queries.rows.player_storage + 1
ret = true
-- TMP: classic_storage is being truncated and populated in C++
self:oldSetStorageValue(old_value_key,value_int)
else
printd("Error: Unrecognized value type: " .. type(value))
end
end
end
else
-- insert (depending on type)
if type(value) == 'string' then
-- update
player_storage_string[pid][key_key] = {
v=new_value_key,
t=1
}
table.insert(storage_log_queries.player_storage_string, {pid, key_key, new_value_key, 1})
storage_log_queries.rows.player_storage_string = storage_log_queries.rows.player_storage_string + 1
ret = true
elseif type(value) == 'number' then
local value_int = math.floor(value) -- Dont allow floats or double numbers
if value_int ~= value then
printd("Warning: setStorageValue parameters converted to whole number.\nsetStorageValue("..key_key..","..value..") changed to setStorageValue("..key_key..","..value_int..")")
end
-- Does anyone else has a classic_storage key for this?
if reserved_table[key_key] ~= nil then
-- yes: re-use that one
player_storage_string[pid][key_key] = {
v=reserved_table[key_key],
t=0
}
table.insert(storage_log_queries.player_storage_string, {pid, key_key, reserved_table[key_key], 0})
storage_log_queries.rows.player_storage_string = storage_log_queries.rows.player_storage_string + 1
-- update or insert classic_storage
if classic_storage[pid] == nil then
classic_storage[pid] = {[reserved_table[key_key]]=value_int}
else
classic_storage[pid][reserved_table[key_key]] = value_int
end
table.insert(storage_log_queries.player_storage, {pid, reserved_table[key_key], value_int})
storage_log_queries.rows.player_storage = storage_log_queries.rows.player_storage + 1
ret = true
-- TMP: classic_storage is being truncated and populated in C++
self:oldSetStorageValue(reserved_table[key_key],value_int)
else
-- no: create a new one
-- generate a new key
res_key = reserved_high + 1
-- Ensure this generated key doesn't conflict with any existing keys
for _, i_res_key in ipairs(reserved_table) do
if i_res_key >= res_key then
printd("Warning: setStorageValue("..key_key..","..value_int..") - Auto allocated res_key ["..res_key.."] already in use. Trying to use: ["..(i_res_key + 1).."] instead.")
res_key = i_res_key + 1
end
end
-- see if allocated res_key is available and within reserved_range
if res_key <= reserved_storage_max then
player_storage_string[pid][key_key] = {
v=res_key,
t=0
}
table.insert(storage_log_queries.player_storage_string, {pid, key_key, res_key, 0})
storage_log_queries.rows.player_storage_string = storage_log_queries.rows.player_storage_string + 1
-- update or insert classic_storage
if classic_storage[pid] == nil then
classic_storage[pid] = {[res_key]=value_int}
else
classic_storage[pid][res_key] = value_int
end
table.insert(storage_log_queries.player_storage, {pid, res_key, value_int})
storage_log_queries.rows.player_storage = storage_log_queries.rows.player_storage + 1
reserved_table[res_key] = value_int
ret = true
-- TMP: classic_storage is being truncated and populated in C++
self:oldSetStorageValue(res_key,value_int)
else
printd("Error: reserved_high: " .. res_key .. " exceeds reserved_storage_max: " .. reserved_storage_max .. "\nOperation cancelled: setStorageValue("..key_key..","..value_int..")")
end
end
else
printd("Error: Unrecognized value type: " .. type(value))
end
end
end
local ret_str = ret and '[true]' or '[false]'
printd(pid..":setStorageValue(".. key ..",".. value ..") = "..ret_str)
return ret
end
CREATE TABLE IF NOT EXISTS `storage_string_keys` (
`string` varchar(255) NOT NULL,
`key` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`string`),
UNIQUE KEY `key` (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE IF NOT EXISTS `storage_string_values` (
`key` int(10) unsigned NOT NULL AUTO_INCREMENT,
`string` varchar(255) NOT NULL,
PRIMARY KEY (`key`),
UNIQUE KEY `string` (`string`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
CREATE TABLE IF NOT EXISTS `player_storage_string` (
`player_id` int(11) NOT NULL DEFAULT '0',
`key_key` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Table: storage_string_values',
`value_key` int(10) unsigned NOT NULL COMMENT 'Key reference in (player_storage) or (storage_string_values) based on type',
`type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0 = int (player_storage), 1 = string (storage_string_values)',
PRIMARY KEY (`player_id`,`key_key`),
FOREIGN KEY (`player_id`) REFERENCES `players`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`key_key`) REFERENCES `storage_string_keys`(`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
local function dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
local talk_dumpStorage = TalkAction("/dumpstorage")
function talk_dumpStorage.onSay(player, words, param)
dumpStorage()
return true
end
talk_dumpStorage:separator(" ")
talk_dumpStorage:register()
local getStorage = TalkAction("/getstorage")
function getStorage.onSay(player, words, param)
local key = tonumber(param)
local print_key = nil
local print_pid = player:getGuid()
if not key then
key = string.lower(param)
print_key = db.escapeString(key)
else
print_key = key
end
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, " ")
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, " "..print_pid..":getStorageValue("..print_key .."): "..dump(player:getStorageValue(key)))
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "OLD "..print_pid..":getStorageValue("..print_key .."): "..dump(player:oldGetStorageValue(key)))
return true
end
getStorage:separator(" ")
getStorage:register()
local setStorage = TalkAction("/setstorage")
function setStorage.onSay(player, words, param)
local parts = param:split(',')
local input = {
key=parts[1],
value=parts[2]
}
local key = tonumber(input.key)
local value = tonumber(input.value)
local print_key = nil
local print_value = nil
local print_pid = player:getGuid()
if not key then
key = string.lower(input.key)
print_key = db.escapeString(key)
else
print_key = key
end
if not value then
value = input.value
print_value = db.escapeString(value)
else
print_value = value
end
player:setStorageValue(key,value)
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, " ")
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, ""..print_pid..":setStorageValue("..print_key ..", ".. print_value..")")
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "=== "..print_pid..":getStorageValue("..print_key .."): "..dump(player:getStorageValue(key)))
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "OLD "..print_pid..":getStorageValue("..print_key .."): "..dump(player:oldGetStorageValue(key)))
return true
end
setStorage:separator(" ")
setStorage:register()
--[[ some tests I used this talkaction for
2:setStorageValue('hello', 'world')
=== 2:getStorageValue('hello'): world
OLD 2:getStorageValue('hello'): -1
2:setStorageValue(1337, 7331)
=== 2:getStorageValue(1337): 7331
OLD 2:getStorageValue(1337): 7331
2:setStorageValue(7331, 'this@is.LEET')
=== 2:getStorageValue(7331): this@is.LEET
OLD 2:getStorageValue(7331): -1
2:setStorageValue(7331, 1337)
=== 2:getStorageValue(7331): 1337
OLD 2:getStorageValue(7331): 1337
2:setStorageValue('woo', 'HOO!')
=== 2:getStorageValue('woo'): HOO!
OLD 2:getStorageValue('woo'): -1
2:setStorageValue('autoloot', 1)
=== 2:getStorageValue('autoloot'): 1
OLD 2:getStorageValue('autoloot'): -1
]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment