Can't set value in table to module?

I tried the following:

        print(require(Arena.CardEnvironment))
	for i , Card in pairs(module.TemporaryCache[playerA.UserId]) do
		Card.ArenaEnvironment = require(Arena.CardEnvironment)
		print(Card.ArenaEnvironment)
	end

and Card.ArenaEnvironment returns nil, but the print before that one returns a table with all the data from the module

Looking at the indentation whatever reference “Card.ArenaEnvironment” is must not be in scope inside the for loop but is in the block of code directly before. In other words “Card.ArenaEnvironment” can be accessed outside of the for loop at that scope level (indicated by the further indentation) but can’t be accessed from within the scope of the body of the for loop itself.

1 Like

Well, Card is a variable that is clearly accessible by that scope, and ArenaEnvironment is a key within Card, so I don’t think scope is the issue here.

for i, Card in

Card is being overridden every time the for loop completes a single cycle/pass. Without more context it’s hard to know what’s going on.

Well, that’s all of the code pertaining to the Card object in that script, though upon further inspection, I am seeing that setting Card.ArenaEnvironment to anything doesn’t work either, so i think it’s an issue with the metatable i placed on it.
(Btw do you actually need to set the key value pair with the __newindex metamethod or does that happen automatically regardless?)

What is Arena and Card? Can you post the whole script?

-- Arena.CardEnvironment is ModuleScript
-- Card.ArenaEnvironment is ModuleScript(?)
local Arena: {CardEnvironment: ModuleScript}
local Card: {ArenaEnvironment: ModuleScript}
local module: {TemporaryCache: {Card}}
print(require(Arena.CardEnvironment)) -- Get CardEnvironment
for _, Card: ModuleScript in pairs(module.TemporaryCache[playerA.UserId]) do
    Card.ArenaEnvironment = require(Arena.CardEnvironment)
    print(Card.ArenaEnvironment)
    -- ArenaEnvironment is not a valid member of ModuleScript
end

Arena is a model, CardEnvironment is a ModuleScript with some a list of cards in what section (i.e: the player’s hand, their deck, the discard pile) aand some functions to access them.
Card would be a custom pseudo class thing, containing each card’s properties, references to it’s model and associated gui object, and functions to run when custom events are triggered by metatables.

Now just forgive me because I still don’t get how to make code readable, i added comments though to see if that helps:


local Card = {} -- Metamethods are found at the end before defaultcards; line 184


function Card.new(Properties , player)
	local self = setmetatable({} , Card) -- Giving acces to module functions
	local newcard = self
	
	self.Name = "Boyparis"

	self.Color = 6865280869
	self.Image = 6873565000
	self.Power = 500
	self.Health = 500
	self.Counters = 0

	self.Slot = "Pending"

	do --Read only properties
		self.WhiteCost = 10
		self.RedCost = 10
		self.BlueCost = 10
		self.GreenCost = 10
		self.YellowCost = 10
		self.CardId = math.random(0x000000 , 0xffffff)
		self.ArenaEnvironment = "Pending"
	end

	self.Owner = player or "Pending"
	self.ClickConnection = "Pending"
	self.Events = {}

	do -- Events
		self.Events.DamageTaken = {}
		self.Events.Destroyed = {}
		self.Events.Targeted = {}
		self.Events.PowerChanged = {}
		self.Events.CountersChanged = {}
		self.Events.TurnEnd = {}
		self.Events.TurnStart = {}
		self.Events.CardDrawn = {}
		self.Events.CardDestroyed = {}
		self.Events.OnSelfCast = {}
		self.Events.OnAllyCast = {}
		self.Events.OnEnemyCast = {}
		self.Events.OnAnyCast = {}
		self.Events.EnemyDestroyed = {}
		self.Events.AllyDestroyed = {}
		self.Events.Transformed = {}
	end

	if Properties then
		for i , v in pairs(Properties) do
			if self[i] then
				self[i] = v
			end
		end
	end

	self.Model = game.ReplicatedStorage.MysteryOrb:Clone()
	self.CardObject = self:RenewGui()

	local proxy = setmetatable({realcard = self} , {
		__index = function(Tab , key) 
			if self[key] then
				if self[key] == "Pending" then print(key .. " is nil") return nil end --Values aren't set to nil so they remain in table, getting them still returns nil though
				
				return self[key]
			end
		end,
		__newindex = function(Tab , key , val) -- This entire function just calls any functions within the card's events
			
			if val == nil then --Don't want to remove properties from table
				val = "Pending"
			end
			
			if self[key] then
				print("Setting key " .. tostring(key) .. " to " .. tostring(val))

				if table.find({"Power", "Health", "WhiteCost", "RedCost", "GreenCost", "BlueCost", "YellowCost", "Counters"} , key) then
					
					local EventTable = nil
					local Args = nil

					if key == "Power" then
						EventTable = self.Events.PowerChanged
						Args = {self , val - self[key]}
					end

					if key == "Health" then
						if self[key] < val then
							EventTable = self.Events.DamageTaken
							Args = {self , val - self[key]}
						elseif val <= 0 then
							EventTable = self.Events.Destroyed
							Args = {self}
						end
					end

					if key == "Counters" then
						EventTable = self.Events.CountersChanged
						Args = {self , val - self[key]}
					end

					self[key] = math.clamp(val , 0 , math.huge)

					if EventTable then

						for i , Effect in pairs(EventTable) do
							Effect(Args)
						end
					end
					
				elseif key == "Slot" then --This only triggers effects and changes the part of the environment the card is in

					local EnvModule = require(self.ArenaEnvironment)
					local PlayerEnv = EnvModule[self.Owner.UserId].Environment

					if PlayerEnv[val] then

						PlayerEnv[key][#PlayerEnv[val] + 1] = self

						if val == "Field" then

							for i , card in pairs(PlayerEnv.Field) do
								for i , effect in pairs(card.Events.OnAnyCast) do
									effect()
								end
								for i , effect in pairs(card.Events.OnAllyCast) do
									effect()
								end
							end

							for i , card in pairs(EnvModule[EnvModule:GetOppositePlayer(self.Owner).UserId].Environment.Field) do
								for i , effect in pairs(card.Events.OnAnyCast) do
									effect()
								end
								for i , effect in pairs(card.Events.OnAllyCast) do
									effect()
								end
							end 
						end

						if val == "Deck" then
							self.Cardobject.Parent = nil
						end

						if val == "Baseplate" then
							self.Cardobject.Parent = nil
						end
					end
				
				elseif key == "ClickConnection" then
					if self[key] then self[key]:Disconnect() end
						
				elseif table.find({"Name", "Color", "Image" , "Owner"} , key) then
				self[key] = val
					
			end
				
			else
				error("Attempt to set invalid key: " .. key)
			end
		end
})
	
	
return proxy
end

function Card:LoadEnvironment(Module)
	self.ArenaEnvironment = require(Module)
	return self.ArenaEnvironment
end


function Card:SpawnModel(slotGui) --Just some visual effect thing
	local effect = game.ReplicatedStorage.CastEffect:Clone() --Spawn effect
	effect:SetPrimaryPartCFrame(slotGui.Parent.CFrame - Vector3.new(0,6,0)) --Make it appear in the correct slot
	effect.Parent = workspace

	local tween = game:GetService("TweenService"):Create(effect.PrimaryPart,TweenInfo.new(0.35),{CFrame = CFrame.new((effect.PrimaryPart.Position + Vector3.new(0,8,0))) * CFrame.Angles(0,math.rad(120),0)}) --Tween 
	tween:Play()
	tween.Completed:Wait()

	local model = self.Model
	model:SetPrimaryPartCFrame(self.Parent.Parent.CFrame)
	model:SetPrimaryPartCFrame(CFrame.new(model.PrimaryPart.Position + Vector3.new(0,6,0)))
	model.Parent = self.CardObject
	model.Humanoid.Animator:LoadAnimation(model.IdleAnimation):Play()

	local tween = game:GetService("TweenService"):Create(effect.PrimaryPart,TweenInfo.new(0.2),{CFrame =(effect.PrimaryPart.CFrame * CFrame.Angles(0,math.rad(240),0))}) --Tween 
	tween:Play()
	tween.Completed:Wait()	

	local tween = game:GetService("TweenService"):Create(effect.PrimaryPart,TweenInfo.new(0.2),{CFrame = (effect.PrimaryPart.CFrame * CFrame.Angles(0,math.rad(360),0))}) --Tween 
	tween:Play()
	tween.Completed:Wait()	
	effect:Destroy()
end

function Card:RenewGui() --Copies a pre-made gui from replicatedstorage and adjusts it

	local InsertService = game:GetService("InsertService")
	local BorderDec =  InsertService:LoadAsset(tonumber(self.Color))
	local DisplayDec =  InsertService:LoadAsset(self.Image)
	local card =  game.ReplicatedStorage.EmptyCard:Clone()
	card.Name = self.Name
	card.NameLabel.Text = self.Name
	card.Border.Image = BorderDec.Decal.Texture
	card.Display.Image = DisplayDec.Decal.Texture 
	BorderDec:Destroy()
	DisplayDec:Destroy()
	card.Power.Text = self.Power
	card.Health.Text = self.Health
	card.ModelVal.Value = self.Model
	local distance = 0.13

	local colors = {"White","Yellow","Green","Blue","Red",}

	for i , color in pairs(colors) do
		card.Costs[color].CostLabel.Text = self[color .. "Cost"]

		if tonumber(card.Costs[color].CostLabel.Text) <= 0 then 
			card.Costs[color].Visible = false
		else
			card.Costs[color].Position = UDim2.new(distance,0,0.91, 0)
			distance += 0.075
		end
	end

	return card
end

local function deepCopy(original) -- function to  copy tables

	local copy = {}
	for k, v in pairs(original) do
		if type(v) == "table" then
			v = deepCopy(v)
		end
		copy[k] = v
	end
	return copy
end

local mt = deepCopy(Card) -- create metatable with functions from module
mt.new = nil -- remove  .new() function to prevent cyclical table reference, also does not have __index metamethod for the same reason.

Card.__index = mt

Card.DefaultCards = {
	Card.new({
		["Name"] = "Boyparis",
		["Color"] = 6865280869,
		["Image"] = 6873565000,
		["Power"] = 400,
		["Health"] = 5000,
		["WhiteCost"] = 10,
		["RedCost"] = 3,
		["BlueCost"] = 3,
		["GreenCost"] = 3,
		["YellowCost"] = 3,
		["Model"] = 6971743255
	}),

	Card.new({
		["Name"] = "Nil",
		["Color"] = 6865283271,
		["Image"] = 6865303020,
		["Power"] = 50,
		["Health"] = 50,
		["WhiteCost"] = 1,
		["RedCost"] = 1,
		["BlueCost"] = 1,
		["GreenCost"] = 0,
		["YellowCost"] = 0,
		["Model"] = 6892715376
	}),
	Card.new({
		["Name"] = "Nil",
		["Color"] = 6865283271,
		["Image"] = 6865303020,
		["Power"] = 50,
		["Health"] = 50,
		["WhiteCost"] = 1,
		["RedCost"] = 1,
		["BlueCost"] = 1,
		["GreenCost"] = 0,
		["YellowCost"] = 0,
		["Model"] = 6892715376
	}),
	Card.new({
		["Name"] = "Nil",
		["Color"] = 6865281448,
		["Image"] = 6865303020,
		["Power"] = 50,
		["Health"] = 50,
		["WhiteCost"] = 1,
		["RedCost"] = 1,
		["BlueCost"] = 1,
		["GreenCost"] = 0,
		["YellowCost"] = 0,
		["Model"] = 6892715376
	}),
	Card.new({
		["Name"] = "Nil",
		["Color"] = 6865282008,
		["Image"] = 6865303020,
		["Power"] = 50,
		["Health"] = 50,
		["WhiteCost"] = 1,
		["RedCost"] = 1,
		["BlueCost"] = 1,
		["GreenCost"] = 0,
		["YellowCost"] = 0,
		["Model"] = 6892715376
	}),
	Card.new({
		["Name"] = "Nil",
		["Color"] = 6865280869,
		["Image"] = 6865303020,
		["Power"] = 50,
		["Health"] = 50,
		["WhiteCost"] = 1,
		["RedCost"] = 1,
		["BlueCost"] = 1,
		["GreenCost"] = 0,
		["YellowCost"] = 0,
		["Model"] = 6892715376
	}),
	Card.new({
		["Name"] = "Nil",
		["Color"] = 6865282709,
		["Image"] = 6865303020,
		["Power"] = 50,
		["Health"] = 50,
		["WhiteCost"] = 1,
		["RedCost"] = 1,
		["BlueCost"] = 1,
		["GreenCost"] = 0,
		["YellowCost"] = 0,
		["Model"] = 6892715376
	}),
}

return Card

That said, i believe my error was that i did not add anything in the newindex metamethod to set the value in the table, because i though that happened automatically.
nope still doesn’t work.