So I’ve been working on a skin system (and it works sorta) but I want to update the table that gives the moves their properties.
For some context here is my attack and properties scripts:
Move:
function module:Move(Player, Input, ...)
if not KeyTable[Player] then
KeyTable[Player] = {}
end
local Character = Player:IsA("Model") and Player or Player.Character
if not Character:FindFirstChild("Humanoid") then return end
if Character.Humanoid.Health <= 0 then return end
local stand = Player.Data.Ability
local Keybinds = Movesets.ExternalKeybinds
local Movesets = Movesets.Movesets
local AbilityValue = stand.Value
local AbilityName = Abilities[stand.Value]
local Move = Movesets[AbilityName][Input] or Keybinds[Player.Name] and Keybinds[Player.Name][Input] or nil
local backupStats = StatsFile[AbilityName][Movesets[AbilityName][Input] or Keybinds[Player.Name] and Keybinds[Player.Name][Input]]
local SkinName = Player.Data.Skin:GetAttribute("SkinName")
local Stats = StatsFile[SkinName or AbilityName][Move]
if SkinName and Stats then
Stats = MoveUtils.DeepCopy(Stats)
Stats.StandName = Abilities[stand.Value]
end
local move = Stats
if move and move.Checks then
--if not Checks.CanAttack(Player,Move,move.Checks) then return end
end
local cbs = Stats and Stats.callbacks or backupStats and backupStats.callbacks or {};
local fin = cbs.finishServ or nil;
if Move and Stats then
local CastMove = true
local resourceBar = nil
local AwakeningKey = string.format("%s_%s_%s", Player.Name, AbilityValue, Move)
if Stats.Awakening and ResourceHandler.HasResourceBar(AwakeningKey) then
local resourceBar = ResourceHandler.GetResourceBar(AwakeningKey)
if resourceBar:GetResource() >= 1 then
resourceBar:RemoveResource(1)
else
CastMove = false
end
end
local strikeCheck, strikeType = CanAttack(Player,Input,Stats.Checks)
if strikeCheck == false then
if not Strikes[Player.Name] then Strikes[Player.Name] = {} end
local strikeCount = Strikes[Player.Name]
table.insert(strikeCount, strikeType)
coroutine.wrap(function()
wait(60)
table.remove(strikeCount, table.find(strikeCount, strikeType))
end)()
if #strikeCount >= 6 then
coroutine.wrap(function()
table.clear(strikeCount)
end)()
end
end
if not Stats.DoNotActivate then
Character.Values.Active.Value = Input
end
if CastMove then
if Stats.Cooldown and not Stats.AfterMove and ((Player:IsA("Player") and Player.Character == Character) or Player:IsA("Model")) then
task.spawn(CreateCD, Player, Input, Stats.Cooldown, Stats.CooldownDelay, Stats)
end
if Moves[AbilityName] and Moves[AbilityName][Move] then
local Stats = StatsFile[AbilityName][Move]
local suc,fail = pcall(Moves[AbilityName][Move], {Player,Stats,Input, ...})
if not suc then
warn(Move, "\n", AbilityName, "\n", fail)
end
end
end
if Stats.Cooldown and Stats.AfterMove and ((Player:IsA("Player") and Player.Character == Character) or Player:IsA("Model")) then
task.spawn(CreateCD, Player, Input, Stats.Cooldown, Stats.CooldownDelay, Stats)
end
if not Stats.DoNotDeactivate then
Character.Values.Active.Value = ""
end
end
end
Stats:
local CS = game:GetService("CollectionService")
local rs = game:GetService("RunService")
local OutputEvent = game.ReplicatedStorage.Remotes.Output
local animation = require(game.ReplicatedStorage.ReplicatedModules.StandAnimate)
local Rem = game.ReplicatedStorage.Remotes.InputFunc
local module = {}
local AbilityID = require(game.ReplicatedStorage.ReplicatedModules.AbilityID)
local Skins = require(game.ReplicatedStorage.ReplicatedModules.Skins)
local Converter = require(game.ReplicatedStorage.ReplicatedModules.AbilityID)
local UserInputService = require(game.ReplicatedStorage.ClientModules.InputTracker)
local junkTable = {}
local gtable = {
["StarPlatinum"] = {};
}
local comboConverter = {
}
local function defaultCombo(Move, defaultTimer)
return function(input, move, char)
local plr = char or game.Players.LocalPlayer
local char = char or plr.Character
local Stand = AbilityID[plr.Data.Ability.Value]
local stats = module.Stats[Stand][Move]
if not CS:HasTag(char,"Summoned") then return end
if not gtable[Stand][plr.Name] then
gtable[Stand][plr.Name] = {}
end
local StandValues = gtable[Stand][plr.Name]
if not StandValues[Move] then
StandValues[Move] = 1
end
if StandValues[input] then return end
StandValues[input] = true
local val = StandValues[Move]
local animSpeed = stats.AnimSpeed or junkTable
animation.playClient(Stand, ( (comboConverter[Stand] and comboConverter[Stand][input]) or input ) .. "-" .. val, nil, animSpeed[val])
plr.Character.Values.Active.Value = "temp"
Rem:InvokeServer(input)
if val == stats.MaxCombo then
StandValues[Move] = 1
else
StandValues[Move] += 1
end
coroutine.wrap(function()
local currentval = StandValues[Move]
wait((defaultTimer or 1.5) + stats.ComboTick)
if currentval == StandValues[Move] then StandValues[Move] = 1 end
end)()
wait(stats.ComboTick)
StandValues[input] = nil
end
end
local function deepCopy(table)
local newtable = {}
for i, v in pairs(table) do
newtable[i] = type(v) == "table" and deepCopy(v) or v
end
return newtable
end
module.Stats = {
["StarPlatinum"] = {
["Summon"] = {
Checks = {CheckSummoned = false,CheckActive = true};
LeaveParts = {"StandHumanoidRootPart","invis"};
FadeIn = 0.3;
Cooldown = 1.5;
AuraColour = ColorSequence.new{
ColorSequenceKeypoint.new(0, Color3.fromRGB(123, 83, 193)),
ColorSequenceKeypoint.new(1, Color3.fromRGB(139, 67, 227))
},
CooldownDelay = 0;
callbacks = {
client = function(p, c, stand)
if not CS:HasTag(c,"Summoned") then
local anim = animation.playClient("StarPlatinum","StandIdle",Enum.AnimationPriority.Idle)
else
animation.stopClient("StarPlatinum","StandIdle")
end
end,
}
};
["Barrage"] = {
Checks = {CheckSummoned = true,CheckActive = true};
Length = 4.022;
Damage = 1;
Hitbox = {Type = "Magnitude";
RelativePart = "Stand.StandHumanoidRootPart",
Size = 4.5;
Offset = CFrame.new(0,0,-4.5)
},
Knockback = {5,0.05};
Cooldown = 10;
Stun = 0.6;
AfterMove = true;
callbacks = {
client = function(p, c, stand)
local Animation = animation.playClient("StarPlatinum","Barrage",Enum.AnimationPriority.Action,nil,c:FindFirstChildWhichIsA("Humanoid"),true)
Animation:AdjustSpeed(3)
local T = tick()
repeat
wait(0.025)
until tick() - T > 4.022 or not UserInputService:IsKeyDown(Enum.KeyCode.E) or not p or not p.Parent or p.Character.Humanoid:GetState() == Enum.HumanoidStateType.Dead
Animation:Stop()
end,
}
};
["ChargePunch"] = {
Checks = {CheckSummoned = true,CheckActive = true};
Cooldown = 15;
Knockback = {50,0.3,Vector3.new(0,35,0)};
Hitbox = {Type = "Magnitude";
RelativePart = "Stand.StandHumanoidRootPart";
Size = 4;
Offset = CFrame.new(0,0,-4.5)
};
Damage = 20,
callbacks = {
client = function(p, c, stand)
if CS:HasTag(c,"Summoned") then
animation.playClient("StarPlatinum","StrongPunch",Enum.AnimationPriority.Action2,nil,c:FindFirstChildWhichIsA("Humanoid"),true)
end
end,
}
};
["Pose"] = {
Checks = {CheckSummoned = true,CheckActive = true};
Cooldown = 2;
callbacks = {
client = function(p, c, stand)
if CS:HasTag(c,"Posing") then
animation.stopClient("StarPlatinum","Pose")
local CD = Instance.new("BinaryStringValue")
CD.Name = "P"
CD.Parent = p.Cooldowns
local CDCount
repeat
wait(0.025)
CDCount = 0
for i,v in pairs(p.Cooldowns:GetChildren()) do
if v.Name == "P" then
CDCount = CDCount + 1
end
end
until CDCount == 2
CD:Destroy()
else
local Anim = animation.playClient("StarPlatinum","PoseTransition",Enum.AnimationPriority.Action2,nil,c:FindFirstChildWhichIsA("Humanoid"),true)
Anim.Stopped:Wait()
animation.playClient("StarPlatinum","Pose",Enum.AnimationPriority.Action3,nil,c:FindFirstChildWhichIsA("Humanoid"),true)
local CD = Instance.new("BinaryStringValue")
CD.Name = "P"
CD.Parent = p.Cooldowns
local CDCount
repeat
wait(0.025)
CDCount = 0
for i,v in pairs(p.Cooldowns:GetChildren()) do
if v.Name == "P" then
CDCount = CDCount + 1
end
end
until CDCount == 2
CD:Destroy()
end
end,
}
};
["TeleportKnife"] = {
Checks = {CheckSummoned = true,CheckActive = true};
Cooldown = 8;
CustomInvoke = function(input,_,c)
local p = c or game.Players.LocalPlayer
local c = c or p.Character
if c.Cooldowns:FindFirstChild("TeleportKnife") then return end
if CS:HasTag(c,"Summoned") then
local Animation = p:IsA("Player") and animation.playClient("StarPlatinum","KnifeThrow") or animation.playClient("StarPlatinum","KnifeThrow",nil,nil,c:FindFirstChildWhichIsA("Humanoid"),true)
Animation:Play()
end
end,
};
["Ground Slam"] = {
Checks = {CheckSummoned = true};
Cooldown = 100;
AuraColour = ColorSequence.new{
ColorSequenceKeypoint.new(0, Color3.fromRGB(229, 121, 43)),
ColorSequenceKeypoint.new(1, Color3.fromRGB(214, 167, 71))
},
AwakenTime = 50,
Awakening = true
};
["Timestop"] = {
Checks = {CheckSummoned = true,CheckActive = true};
Cooldown = 50;
callbacks = {
client = function(p, c, stand)
end,
}
};
["Beatdown"] = {
};
["Star Finger"] = {
Checks = {CheckSummoned = true};
Cooldown = 9;
callbacks = {
client = function(p, c, stand)
end,
}
};
["Drink"] = {
};
["Quote"] = {
Cooldown = 2,
Quotes = {
{"Yare yare daze..",
"rbxassetid://5332928074",
5,
3},
{"I told you I'd fight, and I'm a man of my word.",
"rbxassetid://7259089971",
{
},
2, 2},
{"You really ticked me off..",
"rbxassetid://5572232666",
{
},
5, 2},
{"",
"rbxassetid://5572233210",
{
},
5, 2},
}
};
["LMB Combo"] = {
Checks = {CheckSummoned = true,CheckActive = true},
Cooldown = 0.2;
Stun = 0.75;
Knockback = {5,0.05};
Hitbox = {
Type = "Magnitude";
RelativePart = "Stand.StandHumanoidRootPart";
Size = 4;
Offset = CFrame.new(0, 0, -5)
};
Damage = 8;
MaxCombo = 4;
ComboTick = 0.3;
AnimSpeed = {
[4] = 2;
};
CustomInvoke = defaultCombo("LMB Combo",0.75);
}
};
["TheWorld"] = {
["Summon"] = {
Checks = {CheckSummoned = false,CheckActive = true};
LeaveParts = {"StandHumanoidRootPart"};
FadeIn = 0.3;
Cooldown = 1.5;
AuraColour = ColorSequence.new{
ColorSequenceKeypoint.new(0, Color3.fromRGB(193, 138, 50)),
ColorSequenceKeypoint.new(1, Color3.fromRGB(227, 186, 115))
},
CooldownDelay = 0;
callbacks = {
client = function(p, c, stand)
if not CS:HasTag(c,"Summoned") then
local anim = animation.playClient("TheWorld","StandIdle",Enum.AnimationPriority.Idle)
else
animation.stopClient("TheWorld","StandIdle")
end
end,
}
},
["Quote"] = {
Cooldown = 2,
Quotes = {
{"THIS IS TRULY THE GREATEST HIGH!!",
"rbxassetid://616582888",
7,
3},
{"*very menacing laugh*",
"rbxassetid://2553928874",
5,
3},
{"If this were a game of chess between us, this would easily class as checkmate.",
"rbxassetid://616594208",
5,
3},
}
}
}
}
local moveStats = module.Stats
local function getStats(ability)
return moveStats[ability]
end
local function loadCode(module)
local standCode = require(module)
for i,v in pairs(standCode) do
--print(i,v)
moveStats[i] = v
end
standCode.GetStats = getStats
end
local function iterate(folder)
for i,v in ipairs(folder:GetChildren()) do
if v:IsA("ModuleScript") then
local suc, fail = pcall(loadCode, v)
if not suc then warn(fail, v) end
elseif v:IsA("Folder") then
iterate(v)
end
end
end
iterate(script)
for i,v in pairs(moveStats) do
if not gtable[i] then
gtable[i] = {}
end
end
for stand, moves in pairs(moveStats) do
for i, v in pairs(moves) do
v.MoveName = i
v.StandName = stand
if i == "Quote" then
v.DoNotActivate = true
v.DoNotDeactivate = true
end
end
end
local function recurseCopyTable(table1, table2)
for key, value in pairs(table2) do
if type(value) == "table" then
recurseCopyTable(table1[key], value)
else
table1[key] = value
end
end
end
local function modifyStats(stats, newStats)
for move, value in pairs(newStats) do
if not stats[move] then
warn(move, "not found, creating new stats for it")
stats[move] = deepCopy(value)
else
if value.EraseKey then
stats[move] = nil
continue
elseif value.ShallowCopy then
stats[move] = deepCopy(value)
continue
end
for key, val in pairs(value) do
if type(val) == "table" then
recurseCopyTable(stats[move][key], val)
else
stats[move][key] = val
end
end
end
end
return stats
end
for stand, skins in pairs(Skins.Funcs.GetSkins()) do
local DefaultAbility = Converter[tonumber(stand)]
for id, name in pairs(skins) do
if moveStats[name] and moveStats[name] ~= moveStats[DefaultAbility] then
local stats = deepCopy(moveStats[DefaultAbility])
if rs:IsStudio() then
warn(name, "stats found, copying and overwriting", DefaultAbility, "stats")
end
modifyStats(stats, moveStats[name])
moveStats[name] = stats
else
moveStats[name] = moveStats[DefaultAbility]
end
end
end
local function indexHandler(ParentTable)
return function(t,k)
if k == "ParentTable" then
return ParentTable
end
end
end
for stand, moves in pairs(moveStats) do
for i, v in pairs(moves) do
setmetatable(v, {
__index = indexHandler(moves)
})
end
end
setmetatable(moveStats, {
__index = function(t, k)
warn(k, " stats not found")
end;
})
return module
Skin Stats:
local CS = game:GetService("CollectionService")
local UserInputService = game:GetService("UserInputService")
local OutputEvent = game.ReplicatedStorage.Remotes.Output
local animation = require(game.ReplicatedStorage.ReplicatedModules.StandAnimate)
local Rem = game.ReplicatedStorage.Remotes.InputFunc
local module = {}
local AbilityID = require(game.ReplicatedStorage.ReplicatedModules.AbilityID)
local junkTable = {}
local gtable = {
["StarPlatinum"] = {};
}
local comboConverter = {}
local function defaultCombo(Move, defaultTimer)
return function(input, move, char)
local plr = char or game.Players.LocalPlayer
local char = char or plr.Character
local Stand = AbilityID[plr.Data.Ability.Value]
local stats = module.Stats[Stand][Move]
if not CS:HasTag(char,"Summoned") then return end
if not gtable[Stand][plr.Name] then
gtable[Stand][plr.Name] = {}
end
local StandValues = gtable[Stand][plr.Name]
if not StandValues[Move] then
StandValues[Move] = 1
end
if StandValues[input] then return end
StandValues[input] = true
local val = StandValues[Move]
local animSpeed = stats.AnimSpeed or junkTable
animation.playClient(Stand, ( (comboConverter[Stand] and comboConverter[Stand][input]) or input ) .. "-" .. val, nil, animSpeed[val])
plr.Character.Values.Active.Value = "temp"
Rem:InvokeServer(input)
if val == stats.MaxCombo then
StandValues[Move] = 1
else
StandValues[Move] += 1
end
coroutine.wrap(function()
local currentval = StandValues[Move]
wait((defaultTimer or 1.5) + stats.ComboTick)
if currentval == StandValues[Move] then StandValues[Move] = 1 end
end)()
wait(stats.ComboTick)
StandValues[input] = nil
end
end
module.Stats = {
["OVA"] = {
["Summon"] = {
AuraColour = ColorSequence.new{
ColorSequenceKeypoint.new(0, Color3.fromRGB(221, 111, 33)),
ColorSequenceKeypoint.new(1, Color3.fromRGB(227, 135, 29))
};
}
}
}
local moveStats = module.Stats
for i,v in pairs(module) do
if not gtable[i] then
gtable[i] = {}
end
end
local function loadCode(newModule)
local standCode = require(newModule)
for i,v in pairs(standCode) do
--print(i,v)
module[i] = v
end
end
for i,v in ipairs(script:GetChildren()) do
-- print(v)
local suc, fail = pcall(loadCode, v)
if not suc then warn(fail, v) end
end
return module
The problem is that when I equip the skin the old stats are used and not the new ones after I use deepCopy.
Also, this is my first DevForum post so if there is something wrong please tell me what to change.