Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
1

Stuck with trying to match two keys together to set a value?

Asked by
xPolarium 1388 Moderation Voter
6 years ago

I have a really simple issue that's been keeping me riled the past hour pertaining to looping through two tables and matching their keys up.

On one table, it's getting the children in a folder (They are 6 IntValues but I really want their Name property), and on the other table, its a dictionary of 6 keys with their values (Those values are intergers). What I need it to do is match up the IntValues' names from the first table with the keys on the second table. The two tables are seperated into a Script and ModuleScript but that shouldn't matter.

Script - Where I want to match up the table

--This is a function that fires from a RemoteEvent
local function onSentChosenClass(plr, chosenClass)
    if chosenClass then

        for i, v in pairs(PlayerStatsFolder:FindFirstChild("Attributes"):GetChildren()) do

--Trying to debug the Metatable thats in the ModuleScript. Prints nil. How dandy.
            print(ModuleScript.AttributeMetatable(chosenClass)[v.Name])

--[[ Previous attempt before the use of _index thats commented out but prints the values of the 2nd table. Ignore the if statement, its always false.
        if v.Name == ModuleScript.Classes[chosenClass].classAtt[v.Name] then
            print(ModuleScript.Classes[chosenClass].classAtt[v.Name])
        end
]]

        end
    end
end

ModuleScript - Holds the dictionary and a function that returns the metatable

local settings = {}

--Literally copied/pasted from Wiki
local returnMetatable = {
    _index = function(self, index)
        return index
    end
}

--This returns the table but I don't seem to be getting the right key/attribute
function settings.AttributeMetatable(className)
    setmetatable(settings.Classes[className].classAtt, returnMetatable)
    return returnMetatable
end

settings.Classes = {
    Warrior = {
        classAtt = {
            -- I want the names of the keys (STR, CON, etc)

            STR = 7,
            CON = 5,
            DEF = 6,
            DEX = 4,
            INT = 4,
            LUC = 4
        }
    }
}
return settings

What I have written down and also have reworked alot is using a metatable and _index to return the key. I got this off Roblox Wiki's metamethods but these are so hard to understand for a below average scripter like my self.

After matching the second table's keys with the names of the IntValues' Name property then its simply setting the IntValues' value to the 2nd table's values. Freaking boring and confusing explanation, right?

I don't expect a rework made to my code, yet that with an explanation is all the more plentiful. Thanks.

1 answer

Log in to vote
0
Answered by 6 years ago
Edited 6 years ago

Metatables let you do fancy things and there is almost always an alternative solution. Briefly summarized, the __index metamethod lets you change how a value is accessed and __newindex lets you change how a value is assigned. (Note that in your code, you missed a 2nd underscore -- it's __index, not _index.) Metatables are one of the suggested methods for creating classes (and when you follow the pattern correctly it works fine), but there are alternatives.

In your case, you could make do with simple tables:

--Module Script
local settings = {}
local classes = {
    Warrior = { -- Note: no need for 'classAtt'
        STR = 7,
        CON = 5,
        --etc
    },
    --Other classes here
}
function settings.GetClass(class)
    return classes[class]
end
return settings

Technically you could just return classes instead of settings; then, instead of ModuleScript.GetClass("Warrior") you'd just do ModuleScript["Warrior"]. However, the above method lets you easily add more functionality later.

Your other script:

local Settings = require(ModuleScriptPathHere) -- might as well give it a reasonable name
local function onSentChosenClass(plr, chosenClass)
    if not chosenClass then error("No chosen class") end -- I expect this shouldn't even happen if you don't have a bug; when you're finished debugging, consider kicking the player instead -- they're probably an exploiter (or they discovered a bug you didn't). It's OK to error in a RemoteEvent; the event will still trigger the next time the event is activated.
    local ch = PlayerStatsFolder:FindFirstChild("Attributes"):GetChildren()
    local value
    local class = Settings.GetClass(chosenClass)
    if not class then error("Invalid class: " .. tostring(chosenClass)) end -- like before, an exploiter might trigger this, or you have a bug. Remember that they can theoretically pass in any value they can dream up.
    for i = 1, #ch do -- you can also use ipairs. pairs also works, but is the least efficient option of all of them, in this case. Do whatever you're most comfortable with.
        value = ch[i]
        value.Value = class[value.Name] or error("Class " .. tostring(chosenClass) .. " does not contain value for " .. tostring(value.Name)) -- this error is solely to make sure you didn't mis-configure anything
    end
end

Since you're already using ModuleScripts, you should consider forgetting about the IntValues and just using the 'settings' ModuleScript of yours. If you intend to increase the value of these IntValues as players level up, you would need another ModuleScript to maintain the current value of everyone (replacing what the IntValues do), but that would arguably be an improvement. (If you're struggling with ModuleScripts, it's fine to keep them as IntValues, especially if the rest of your scripts already depend on them.)

Due to how you have the for loop set up (to iterate over the children), you can safely add other entries into the Warrior table that have nothing to do with the 6 IntValues. You can even add functions, which makes it start looking like a class. If you do want class functionality, you might want to study different ways of making them:

http://lua-users.org/wiki/ObjectOrientedProgramming

The style I use is this:

function MyClass(value)
    local self = {}
    local valueIsEven = value % 2 == 0
    function self:GetValue() -- technically you could use "." instead of ":" since you never need 'self' passed in to the function, but to mimic Roblox notation I use ":"
        return value
    end
    function self:Increment()
        value = value + 1
        valueIsEven = value % 2 == 0 -- actually it would probably be more efficient to calculate this on request rather than here, but this is just an example
    end
    function self:ValueIsEven()
        return valueIsEven
    end
    return self
end
local obj = MyClass(5)
obj:Increment()
print(obj:GetValue(), obj:ValueIsEven()) -- 6, true

--Let's say you wanted a class that inherits from MyClass and also saves the previous value:

function MyClass2(value)
    local self = MyClass(value)
    local baseIncrement = self.Increment
    local prevValue = value
    function self:Increment()
        prevValue = value
        baseIncrement(self)
    end
    function self:GetPrevValue()
        return prevValue
    end
    return self
end
local obj = MyClass2(10)
print(obj:GetPrevValue(), obj:GetValue(), obj:ValueIsEven()) -- 10, 10, true
obj:Increment()
print(obj:GetPrevValue(), obj:GetValue(), obj:ValueIsEven()) -- 10, 11, false
obj:Increment()
print(obj:GetPrevValue(), obj:GetValue(), obj:ValueIsEven()) -- 11, 12, true
0
Much thanks but I had it figured out how to solve this during college class today and, funny enough, its identical to your answer. Also, the plan was to structure this using OOP but I'm just messing around for now. Thanks again. xPolarium 1388 — 6y
Ad

Answer this question