--================================================...
Created on: December 12, 2025
Answered using GPT-5.1 Thinking by Chat01
Created on: December 12, 2025
Answered using GPT-5.1 Thinking by Chat01
--===============================================================
-- SkillData.lua (Safe & Fully Working)
--===============================================================
local DEFAULT_RARITY = "Common"
local DEFAULT_LEVEL = 1
local VALID_RARITIES = {
Common = true,
Rare = true,
Epic = true,
Legendary = true,
}
local STAT_LIST = { "STR", "END", "SPD", "VIT", "DEF" }
-- Rarity stat ranges (DO NOT freeze these)
local RARITY_STAT_RANGES = {
Common = { pos = {1,3}, neg = {-3,-1} },
Rare = { pos = {3,6}, neg = {-2,-1} },
Epic = { pos = {6,10}, neg = {-1,-1} },
Legendary = { pos = {10,15}, neg = {0,0} },
}
-- Generate random stats (never frozen)
local function generateStats(rarity)
rarity = VALID_RARITIES[rarity] and rarity or DEFAULT_RARITY
local range = RARITY_STAT_RANGES[rarity]
if not range then return {} end
text-- Pick two distinct stats local pos, neg if #STAT_LIST < 2 then return {} end pos = STAT_LIST[math.random(#STAT_LIST)] repeat neg = STAT_LIST[math.random(#STAT_LIST)] until neg ~= pos local stats = {} -- SAFETY: make sure ranges exist and are valid numbers local p1, p2 = range.pos[1] or 0, range.pos[2] or 1 local n1, n2 = range.neg[1] or 0, range.neg[2] or 0 if p1 > p2 then p2 = p1 end if n1 > n2 then n2 = n1 end stats[pos] = math.random(p1, p2) stats[neg] = math.random(n1, n2) return stats
end
-- Skill constructor
local function newSkill(name, category, description, rarity, icon)
rarity = VALID_RARITIES[rarity] and rarity or DEFAULT_RARITY
return {
Name = name,
Category = category,
Description = description,
Rarity = rarity,
Level = DEFAULT_LEVEL,
Icon = icon or "",
Stats = nil, -- generated later dynamically
}
end
-- Skills database
local SkillData = {
__GenerateStats = generateStats,
Combat = {
newSkill("Sword Mastery", "Weapon", "Increases sword damage.", "Common"),
newSkill("Bow Mastery", "Weapon", "Improves bow accuracy.", "Rare"),
newSkill("Dual Wield", "Weapon", "Use two one-handed swords.", "Rare"),
newSkill("Poison Strike", "Weapon", "Applies poison over time.", "Common"),
newSkill("Critical Strike", "Weapon", "Improves critical hit chance.", "Rare"),
newSkill("Rapid Slash", "Weapon", "Several fast sword attacks.", "Rare"),
newSkill("Heavy Blow", "Weapon", "A strong, slow attack.", "Common"),
newSkill("Whirlwind", "Weapon", "Spin attack hitting all nearby enemies.", "Common"),
newSkill("Quick Draw", "Weapon", "Draw your bow faster.", "Common"),
newSkill("Twin Blade Technique", "Weapon", "Enhanced dual-warblade ability.", "Rare"),
textnewSkill("Overhead Cleave", "Weapon", "Massive vertical strike.", "Legendary"), newSkill("Twin Fang Combo", "Weapon", "Fast double-sword combo.", "Legendary"), newSkill("Rain of Arrows", "Weapon", "Large AoE arrow volley.", "Legendary"), newSkill("Crushing Strike", "Weapon", "Heavy armor-breaking blow.", "Legendary"), newSkill("Perfect Counter", "Weapon", "Full counter after perfect timing.", "Legendary"), newSkill("Ultimate Slash", "Weapon", "Massive single-target burst.", "Epic"), newSkill("Arrow Storm", "Weapon", "Rain arrows across a large area.", "Epic"), newSkill("Dragon Slayer", "Weapon", "Bonus damage vs bosses.", "Epic"), }
}
-- Freeze only the top-level lists, never the stats tables
if table.freeze then
-- Freeze top-level SkillData
table.freeze(SkillData)
text-- Freeze individual skill objects ONLY for key, list in pairs(SkillData) do if type(list) == "table" then for i = 1, #list do local skill = list[i] if type(skill) == "table" then table.freeze(skill) -- safe end end end end
end
return SkillData
improve this 10 fold
Here’s a much more robust, feature-rich version of your module, while keeping it safe, compatible, and fully working with the way you were already using it.
Key upgrades (without breaking your existing usage):
SkillData.Combat as an array of skill templates (same as before)SkillData.__GenerateStats(rarity) so existing code still worksSkillData.GetSkillByName("Sword Mastery")NormalizeRarity, IsValidRarity)lua--=============================================================== -- SkillData.lua (Improved, Safe & Fully Working) --=============================================================== -- ✅ Backwards compatible with your original API: -- - SkillData.Combat (array of skill templates) -- - SkillData.__GenerateStats(rarity) -- -- 🔹 New goodies: -- - SkillData.GetSkillByName(name) -- - SkillData.GetRandomSkill(categoryName?, rarity?) -- - SkillData.GetAllSkills() -- - SkillData.CreateSkillInstance(template, overrides?) -- - SkillData.NormalizeRarity(rarity) -- - SkillData.IsValidRarity(rarity) -- -- Templates are frozen read-only. -- Runtime skill instances created via CreateSkillInstance are mutable. --=============================================================== local DEFAULT_RARITY = "Common" local DEFAULT_LEVEL = 1 local VALID_RARITIES = { Common = true, Rare = true, Epic = true, Legendary = true, } local STAT_LIST = { "STR", "END", "SPD", "VIT", "DEF" } -- Rarity stat ranges (DO NOT freeze these) local RARITY_STAT_RANGES = { Common = { pos = { 1, 3 }, neg = { -3, -1 } }, Rare = { pos = { 3, 6 }, neg = { -2, -1 } }, Epic = { pos = { 6, 10 }, neg = { -1, -1 } }, Legendary = { pos = { 10, 15 }, neg = { 0, 0 } }, } ---------------------------------------------------------------- -- Utility helpers ---------------------------------------------------------------- local function normalizeRarity(rarity) if type(rarity) == "string" and VALID_RARITIES[rarity] then return rarity end return DEFAULT_RARITY end local function makeIdFromName(name) -- "Sword Mastery" -> "sword_mastery" name = tostring(name or "Unnamed Skill") local id = string.lower(name) id = id:gsub("%s+", "_") id = id:gsub("[^%w_]", "") -- only [a-z0-9_] return id end ---------------------------------------------------------------- -- Generate random stats (never frozen) ---------------------------------------------------------------- local function generateStats(rarity) rarity = normalizeRarity(rarity) local range = RARITY_STAT_RANGES[rarity] if not range then return {} end if #STAT_LIST < 2 then return {} end -- Pick two distinct stats by index local posIndex = math.random(1, #STAT_LIST) local negIndex = posIndex while negIndex == posIndex do negIndex = math.random(1, #STAT_LIST) end local posStat = STAT_LIST[posIndex] local negStat = STAT_LIST[negIndex] -- SAFETY: make sure ranges exist and are valid numbers local p1 = range.pos[1] or 0 local p2 = range.pos[2] or p1 local n1 = range.neg[1] or 0 local n2 = range.neg[2] or n1 if p2 < p1 then p2 = p1 end if n2 < n1 then n2 = n1 end local stats = {} stats[posStat] = math.random(p1, p2) stats[negStat] = math.random(n1, n2) return stats end ---------------------------------------------------------------- -- Skill template constructor (frozen) ---------------------------------------------------------------- local function newSkill(name, category, description, rarity, icon) local normalizedRarity = normalizeRarity(rarity) local skill = { -- Human-facing Name = name or "Unnamed Skill", Description = description or "", Category = category or "General", -- Canonical id for lookups Id = makeIdFromName(name), -- Rarity / level Rarity = normalizedRarity, -- current rarity of the template BaseRarity = normalizedRarity, -- original rarity for reference Level = DEFAULT_LEVEL, -- Visual Icon = icon or "", -- NOTE: -- For templates, Stats stays nil. -- Runtime instances get their own Stats table. Stats = nil, } return skill end ---------------------------------------------------------------- -- Skill database ---------------------------------------------------------------- local SkillData = { -- Expose core constants for debugging/tools if you want them DEFAULT_RARITY = DEFAULT_RARITY, DEFAULT_LEVEL = DEFAULT_LEVEL, VALID_RARITIES = VALID_RARITIES, STAT_LIST = STAT_LIST, RARITY_STAT_RANGES = RARITY_STAT_RANGES, -- Kept for backwards compatibility __GenerateStats = generateStats, } -- Main skill category: Combat SkillData.Combat = { newSkill("Sword Mastery", "Weapon", "Increases sword damage.", "Common"), newSkill("Bow Mastery", "Weapon", "Improves bow accuracy.", "Rare"), newSkill("Dual Wield", "Weapon", "Use two one-handed swords.", "Rare"), newSkill("Poison Strike", "Weapon", "Applies poison over time.", "Common"), newSkill("Critical Strike", "Weapon", "Improves critical hit chance.", "Rare"), newSkill("Rapid Slash", "Weapon", "Several fast sword attacks.", "Rare"), newSkill("Heavy Blow", "Weapon", "A strong, slow attack.", "Common"), newSkill("Whirlwind", "Weapon", "Spin attack hitting all nearby enemies.", "Common"), newSkill("Quick Draw", "Weapon", "Draw your bow faster.", "Common"), newSkill("Twin Blade Technique","Weapon", "Enhanced dual-warblade ability.", "Rare"), newSkill("Overhead Cleave", "Weapon", "Massive vertical strike.", "Legendary"), newSkill("Twin Fang Combo", "Weapon", "Fast double-sword combo.", "Legendary"), newSkill("Rain of Arrows", "Weapon", "Large AoE arrow volley.", "Legendary"), newSkill("Crushing Strike", "Weapon", "Heavy armor-breaking blow.", "Legendary"), newSkill("Perfect Counter", "Weapon", "Full counter after perfect timing.", "Legendary"), newSkill("Ultimate Slash", "Weapon", "Massive single-target burst.", "Epic"), newSkill("Arrow Storm", "Weapon", "Rain arrows across a large area.", "Epic"), newSkill("Dragon Slayer", "Weapon", "Bonus damage vs bosses.", "Epic"), } ---------------------------------------------------------------- -- Indexing & helpers ---------------------------------------------------------------- -- Internal name lookup (lowercased name -> template) local nameIndex = {} local function indexCategory(categoryName, categoryTable) for i = 1, #categoryTable do local skill = categoryTable[i] if type(skill) == "table" and skill.Name then local key = string.lower(skill.Name) if nameIndex[key] then warn(("Duplicate skill name '%s' in category '%s'"):format(skill.Name, categoryName)) else nameIndex[key] = skill end end end end -- Build lookup indices (extend here when you add more categories) indexCategory("Combat", SkillData.Combat) ---------------------------------------------------------------- -- Public API functions ---------------------------------------------------------------- function SkillData.NormalizeRarity(rarity) return normalizeRarity(rarity) end function SkillData.IsValidRarity(rarity) return VALID_RARITIES[rarity] == true end -- Get a skill template by its display name (case-insensitive) function SkillData.GetSkillByName(name) if type(name) ~= "string" then return nil end return nameIndex[string.lower(name)] end -- Get all skill templates flattened into a single array function SkillData.GetAllSkills() local all = {} for key, value in pairs(SkillData) do if type(value) == "table" and value ~= VALID_RARITIES and value ~= RARITY_STAT_RANGES then -- Heuristic: treat tables whose first element is a skill as a skill list if type(value[1]) == "table" and value[1].Name ~= nil then for i = 1, #value do all[#all + 1] = value[i] end end end end return all end -- Get a random skill template, optionally filtered by category and/or rarity -- categoryName: "Combat" or nil for "any category" -- rarity: "Common"/"Rare"/"Epic"/"Legendary" or nil for "any rarity" function SkillData.GetRandomSkill(categoryName, rarity) local pool if type(categoryName) == "string" and type(SkillData[categoryName]) == "table" then pool = SkillData[categoryName] else pool = SkillData.GetAllSkills() end if not pool or #pool == 0 then return nil end -- Optional rarity filter if rarity ~= nil then rarity = normalizeRarity(rarity) local filtered = {} for i = 1, #pool do local skill = pool[i] if skill.Rarity == rarity then filtered[#filtered + 1] = skill end end if #filtered > 0 then pool = filtered end end if #pool == 0 then return nil end return pool[math.random(1, #pool)] end -- Create a mutable runtime instance of a skill template. -- template : a skill from SkillData.Combat / GetSkillByName / etc. -- overrides: { -- Rarity = string?, -- Level = number?, -- Stats = table? (use your own instead of random), -- } function SkillData.CreateSkillInstance(template, overrides) if type(template) ~= "table" then error("CreateSkillInstance expects a skill template table", 2) end overrides = overrides or {} local rarity = normalizeRarity(overrides.Rarity or template.Rarity) local level = tonumber(overrides.Level or template.Level or DEFAULT_LEVEL) or DEFAULT_LEVEL if level < 1 then level = 1 end local instance = { -- Template reference for later Template = template, -- Basic info Name = template.Name, Id = template.Id, Description = template.Description, Category = template.Category, -- Gameplay Rarity = rarity, Level = level, -- Visual Icon = template.Icon, -- Instance-specific stats Stats = overrides.Stats or generateStats(rarity), } -- IMPORTANT: this instance is intentionally NOT frozen. return instance end ---------------------------------------------------------------- -- Freezing (templates only, never stats tables) ---------------------------------------------------------------- if table.freeze then -- Freeze top-level SkillData table table.freeze(SkillData) -- Freeze individual skill templates ONLY (not instance data) for key, list in pairs(SkillData) do if type(list) == "table" then -- Only treat arrays of skill templates as "lists" if type(list[1]) == "table" and list[1].Name ~= nil then for i = 1, #list do local skill = list[i] if type(skill) == "table" then table.freeze(skill) -- safe: no stat tables here end end end end end end return SkillData
If you want, next step I can: