Retrieve an attribute from the same table in LUA - lua

I would like to know if it was possible to retrieve the attribute of a table in the same array. For example here I want to retrieve the "list.index" attribute of my array, how to do?
{
category_title = "Weapon",
description = nil,
type = "list",
list = {items = {"Give", "Remove"}, index = 1},
style = {},
action = {
onSelected = function()
if list.index == 1 then
-- it's 1
else
-- it's 2
end
end
},

It's not possible to use an entry in another entry when the table is being created.
But since you're defining a function, you can do this:
onSelected = function(self)
if self.list.index == 1 then
-- it's 1
else
-- it's 2
end
end
Just make sure you call onSelected with the table as an argument.

Alternatively, you may set the function after constructing the table in order to be able to access the table as an upvalue (as opposed to leveraging table constructors):
local self = {
categoryTitle = "Weapon",
description = nil,
type = "list",
list = {items = {"Give", "Remove"}, index = 1},
style = {},
action = {}
}
function self.action.onSelected()
if self.list.index == 1 then
-- it's 1
else
-- it's 2
end
end
That way, you get self as an upvalue and don't need to pass it as an argument.

Related

Editing multidimensional table with uncertain dimensions in Lua

I want to be able to access and edit values in a user-generated table, that can have any number of dimensions.
Say, for this nested table,
table = {
'1',
{
'2.1',
'2.2'
},
{
{
'3.1.1',
'3.1.2'
},
'3.2'
},
}
I would have another table that contains a location for the needed data,
loc = {3, 1, 2}
Ideally, what I'd want is to be able to not only access but edit the values in the table, similar to using table[3][1][2] but by utilizing the loc table,
print(table[loc[1]][loc[2]][loc[3]]) --returns 3.1.2
print(table[loc]) --hypothetically something like this that takes each indexed member of the table in order
I also want to be able to edit this table.
table[loc] = {'3.1.2.1', '3.1.2.2'}
I need to be able to edit the global table, so cannot use the methods listed in this reddit thread, and haven't been able to find the right way to use metatables for this yet. I appreciate the help, thanks.
I think you could simply write an additional function for this purpose.
function TreeGetValue (Tree, Location)
local CorrectTree = true
local Index = 1
local Dimensions = #Location
local SubTable = Tree
local Value
-- Find the most deep table according to location
while (CorrectTree and (Index < Dimensions)) do
local IndexedValue = SubTable[Location[Index]]
if (type(IndexedValue) == "table") then
SubTable = IndexedValue
Index = Index + 1
else
CorrectTree = false
end
end
-- Get the last value, regarless of the type
if CorrectTree then
Value = SubTable[Location[Index]]
end
return Value
end
Here, we assume that the tree is well-formatted as the beginning. If we find any problem we set the flag CorrectTree to false in order to stop immediately.
We need to make sure we have a table at every dimension in order index a value from.
> TreeGetValue(table, loc)
3.1.2
Obviously, it's also easy to to write the set function:
function TreeSetValue (Tree, Location, NewValue)
local Index = 1
local Dimensions = #Location
local SubTable = Tree
-- Find the most deep table according to location
while (Index < Dimensions) do
local IndexedValue = SubTable[Location[Index]]
-- Create a new sub-table if necessary
if (IndexedValue == nil) then
IndexedValue = {}
SubTable[Location[Index]] = IndexedValue
end
SubTable = IndexedValue
Index = Index + 1
end
-- Set or replace the previous value
SubTable[Location[Index]] = NewValue
end
And then to test it with your test data:
> TreeGetValue(table, loc)
3.1.2
> TreeSetValue(table, loc, "NEW-VALUE")
> TreeGetValue(table, loc)
NEW-VALUE

Adding objects to array on Lua

First of all I'm new on lua, I'm trying to implement a simple shopping cart as an object oriented exercise.
So I defined a Cart Object which stores several items objects
Cart = {
items = {},
discount = 0
}
function Cart:new(discount)
local object = {}
setmetatable(object, {__index = self})
self.discount = discount
return object
end
function Cart:addItem(item)
table.insert(self.items, item)
end
function Cart:GetTotal()
local total = 0
for i = 1, #self.items do
total = total + self.items[i]:GetPrice()
end
return total - self.discount
end
Each Item has the responsibility of calculate their price:
Item = {
units = 0,
pricePerUnit = 5,
name = ""
}
function Item:new(units, pricePerUnit, name)
local object = {}
setmetatable(object, {__index = self})
self.units = units
self.pricePerUnit = pricePerUnit
self.name = name
return object
end
function Item:GetPrice()
return self.units * self.pricePerUnit
end
But when I create the object and add items I get 60 as result, When I debugged the script I found that all the elements of the table are identical as if they were overwritten, could someone explain to me why and how can it be solved? Thanks in advance.
local shoppingCart = Cart:new(0)
shoppingCart:addItem(Item:new(1, 10, "Oranges"))
shoppingCart:addItem(Item:new(1, 15, "lemons"))
shoppingCart:addItem(Item:new(1, 20, "Strawberries"))
print(shoppingCart:GetTotal())
Cart:new and Item:new are meant to create new objects, therefore you call them on the classes themselves rather than on instances. That means the self that gets passed to those methods is those classes.
In both of those methods, you create an object table to become the new object, so you need to set the fields on that object, instead of modifying the class, eg, object.discount = discount.

dictionary help/DataStore

The Issue is I have a dictionary that holds all my data and its supposed to be able to turn into a directorys in replicated storage with all the values being strings then turn back into a dictionary with all the keys when the player leave. However, I cant figure out how to turn into a dictionary(With keys).
ive sat for a few hours testing things but after the first layer of values I cant get figure out a way to get the deeper values and keys into the table
local DataTable =
{
["DontSave_Values"] =
{
["Stamina"] = 100;
};
["DontSave_Debounces"] =
{
};
["TestData"] = 1;
["Ship"] =
{
["Hull"] = "Large_Ship";
["Mast"] = "Iron_Tall";
["Crew"] =
{
["Joe One"] =
{
["Shirt"] = "Blue";
["Pants"] = "Green"
};
["Joe Two"] =
{
["Shirt"] = "Silver";
["Pants"] = "Brown";
["Kids"] =
{
["Joe Mama1"] =
{
["Age"] = 5
};
["Joe Mama2"]=
{
["Age"] = 6
};
}
};
}
};
["Level"] =
{
};
["Exp"] =
{
};
}
------Test to see if its an array
function isArray(Variable)
local Test = pcall(function()
local VarBreak = (Variable.." ")
end)
if Test == false then
return true
else
return false
end
end
------TURNS INTO FOLDERS
function CreateGameDirectory(Player, Data)
local mainFolder = Instance.new("Folder")
mainFolder.Parent = game.ReplicatedStorage
mainFolder.Name = Player.UserId
local function IterateDictionary(Array, mainFolder)
local CurrentDirectory = mainFolder
for i,v in pairs(Array) do
if isArray(v) then
CurrentDirectory = Instance.new("Folder", mainFolder)
CurrentDirectory.Name = i
for o,p in pairs(v) do
if isArray(p) then
local TemporaryDir = Instance.new("Folder", CurrentDirectory)
TemporaryDir.Name = o
IterateDictionary(p, TemporaryDir)
else
local NewValue = Instance.new("StringValue", CurrentDirectory)
NewValue.Name = o
NewValue.Value = p
end
end
else
local value = Instance.new("StringValue", mainFolder)
value.Name = i
value.Value = v
end
end
end
IterateDictionary(Data, mainFolder)
end
------To turn it back into a table
function CreateTable(Player)
local NewDataTable = {}
local Data = RS:FindFirstChild(Player.UserId)
local function DigDeep(newData, pData, ...)
local CurrentDir = newData
for i,v in pairs(pData:GetChildren()) do
if string.sub(v.Name,1,8) ~= "DontSave" then
end
end
end
DigDeep(NewDataTable, Data)
return NewDataTable
end
I expected to when the player leaves run createtable function and turn all the instances in replicated storage back into a dictionary with keys.
Why not just store extra information in your data table to help make it easy to convert back and forth. As an example, why not have your data look like this :
local ExampleData = {
-- hold onto your special "DON'T SAVE" values as simple keys in the table.
DONTSAVE_Values = {
Stamina = 0,
},
-- but every element under ReplicatedStorage will be used to represent an actual Instance.
ReplicatedStorage = {
-- store an array of Child elements rather than another map.
-- This is because Roblox allows you to have multiple children with the same name.
Name = "ReplicatedStorage",
Class = "ReplicatedStorage",
Properties = {},
Children = {
{
Name = "Level",
Class = "NumberValue",
Properties = {
Value = 0,
},
Children = {},
},
{
Name = "Ship",
Class = "Model",
Properties = {},
Children = {
{
-- add all of the other instances following the same pattern :
-- Name, Class, Properties, Children
},
},
},
}, -- end list of Children
}, -- end ReplicatedStorage element
};
You can create this table with a simple recursive function :
-- when given a Roblox instance, generate the dataTable for that element
local function getElementData(targetInstance)
local element = {
Name = targetInstance.Name,
Class = targetInstance.ClassName,
Properties = {},
Children = {},
}
-- add special case logic to pull out specific properties for certain classes
local c = targetInstance.ClassName
if c == "StringValue" then
element.Properties = { Value = targetInstance.Value }
-- elseif c == "ADD MORE CASES HERE" then
else
warn(string.format("Not sure how to parse information for %s", c))
end
-- iterate over the children and populate their data
for i, childInstance in ipairs(targetInstance:GetChildren()) do
table.insert( element.Children, getElementData(childInstance))
end
-- give the data back to the caller
return element
end
-- populate the table
local Data = {
ReplicatedStorage = getElementData(game.ReplicatedStorage)
}
Now Data.ReplicatedStorage.Children should have a data representation of the entire folder. You could even save this entire table as a string by passing it to HttpService:JSONEncode() if you wanted to.
When you're ready to convert these back into instances, use the data you've stored to give you enough information on how to recreate the elements :
local function recreateElement(tableData, parent)
-- special case ReplicatedStorage
if tableData.Class == "ReplicatedStorage" then
-- skip right to the children
for i, child in ipairs(tableData.Children) do
recreateElement(child, parent)
end
-- quick escape from this node
return
end
-- otherwise, just create elements from their data
local element = Instance.new(tableData.Class)
element.Name = tableData.Name
-- set all the saved properties
for k, v in pairs(tableData.Properties) do
element[k] = v
end
-- recreate all of the children of this element
for i, child in ipairs(tableData.Children) do
recreateElement(child, element)
end
-- put the element into the workspace
element.Parent = parent
end
-- populate the ReplicatedStorage from the stored data
recreateElement( Data.ReplicatedStorage, game.ReplicatedStorage)
You should be careful about how and when you choose to save this data. If you are playing a multiplayer game, you should be careful that this kind of logic only updates ReplicatedStorage for the first player to join the server. Otherwise, you run the risk of a player joining and overwriting everything that everyone else has been doing.
Since there isn't a way to iterate over properties of Roblox Instances, you'll have to manually update the getElementData function to properly store the information you care about for each type of object. Hopefully this helps!
If anyone else has this problem the solution I used was just to convert the instances strait into JSON format(I used the names as keys). so that way I can save it, and then when the player rejoins I just used JSONDecode to turn it into the dictionary I needed.
function DirToJSON(Player)
local NewData = RS:FindFirstChild(Player.UserId)
local JSONstring="{"
local function Recurse(Data)
for i, v in pairs(Data:GetChildren()) do
if v:IsA("Folder") then
if #v:GetChildren() < 1 then
if i == #Data:GetChildren()then
JSONstring=JSONstring..'"'..v.Name..'":[]'
else
JSONstring=JSONstring..'"'..v.Name..'":[],'
end
else
JSONstring=JSONstring..'"'..v.Name..'":{'
Recurse(v)
if i == #Data:GetChildren()then
JSONstring=JSONstring..'}'
else
JSONstring=JSONstring..'},'
end
end
else
if i == #Data:GetChildren()then
JSONstring=JSONstring..'"'..v.Name..'":"'..v.Value..'"'
else
JSONstring=JSONstring..'"'..v.Name..'":"'..v.Value..'",'
end
end
end
end
Recurse(NewData)
JSONstring = JSONstring.."}"
return(JSONstring)
end

Lua table not accessible (attempt to index a nil value)

I have a table that looks like this:
{
block_0 = {
hash = "98d1a61c4e3d6394b2970a2a5c44ec2caf172ad5c6844b114867b31fa528220e",
index = 0
}
}
Shouldn't I be able to access the index and hash values of block_0 by saying chain["block_0"]["hash"]? It is not working. When I use this line, I get the error attempt to index a nil value (field 'block_0'). How can I properly access hash and index?
EDIT: Here is some more context:
function add_thing()
block_name = "block_0"
block = { }
block[block_name] = { }
block[block_name]["hash"] = ""
block[block_name]["index"] = ""
block[block_name]["hash"] = "this is a test hash"
block[block_name]["index"] = 10
return block
end
chain = { }
table.insert(chain, add_thing())
require 'pl.pretty'.dump(chain)
You are inserting the return value of add_thing into chain. Thus chain is now a table of tables. To index the correct field you have to index chain first, i.e. chain[1]["block_0"]["hash"]. I rather suspect that this is not the intended behaviour and you want to do the following
local function add_thing(chain)
local block_name = "block_0"
chain[block_name] = {
hash = "this is a test hash",
index = 10
}
end
local chain = {}
add_thing(chain)
print(chain["block_0"]["hash"]) -- this is a test hash
Live on Wandbox
This works as expected because tables are reference types.

How can I implement a read-only table in lua?

I wrote an example.
function readOnly(t)
local newTable = {}
local metaTable = {}
metaTable.__index = t
metaTable.__newindex = function(tbl, key, value) error("Data cannot be changed!") end
setmetatable(newTable, metaTable)
return newTable
end
local tbl = {
sex = {
male = 1,
female = 1,
},
identity = {
police = 1,
student = 2,
doctor = {
physician = 1,
oculist = 2,
}
}
}
local hold = readOnly(tbl)
print(hold.sex)
hold.sex = 2 --error
It means that I can give access to the field of the table "tbl" but at the same time, I cannot change the value related to the field.
Now, the problem is that I wanna let all the nested tables own this read-only
property.How can I improve the "readOnly" method?
You just have to apply your readOnly function recursively to the inner table fields as well. You can do so on-access in the __index metamethod. You should also cache the readonly proxy tables that you create, otherwise any read access to inner tables (e.g. hold.sex) will create a new proxy table.
-- remember mappings from original table to proxy table
local proxies = setmetatable( {}, { __mode = "k" } )
function readOnly( t )
if type( t ) == "table" then
-- check whether we already have a readonly proxy for this table
local p = proxies[ t ]
if not p then
-- create new proxy table for t
p = setmetatable( {}, {
__index = function( _, k )
-- apply `readonly` recursively to field `t[k]`
return readOnly( t[ k ] )
end,
__newindex = function()
error( "table is readonly", 2 )
end,
} )
proxies[ t ] = p
end
return p
else
-- non-tables are returned as is
return t
end
end

Resources