|
| 1 | +---@alias OxSelectorItem {[1]: number, [2]: any} |
| 2 | +---@alias OxSelectorSet OxSelectorItem[] |
| 3 | + |
| 4 | +---@class OxSelector: OxClass |
| 5 | +---@field private sets OxSelectorSet | table<string, OxSelectorItem[]> |
| 6 | +---@field private totalWeights table<string, number> |
| 7 | +local OxSelector = lib.class("OxSelector") |
| 8 | + |
| 9 | +local DEFAULT_SET = 'default' |
| 10 | +local deepClone = lib.table.deepclone |
| 11 | + |
| 12 | + |
| 13 | +local function calculateTotalWeight(set) |
| 14 | + local total = 0 |
| 15 | + for i = 1, #set do |
| 16 | + local item = set[i] |
| 17 | + assert(type(item) == "table", "Each OxSelectorItem must be a table") |
| 18 | + local weight = item[1] |
| 19 | + assert(type(weight) == "number" and weight >= 0, "weight must be 0 or more") |
| 20 | + total += weight |
| 21 | + end |
| 22 | + return total |
| 23 | +end |
| 24 | + |
| 25 | + |
| 26 | +---@param sets OxSelectorSet | table<string, OxSelectorItem[]> |
| 27 | +function OxSelector:constructor(sets) |
| 28 | + if type(sets) ~= "table" then |
| 29 | + lib.print.error("Invalid sets provided to OxSelector constructor") |
| 30 | + end |
| 31 | + |
| 32 | + if lib.table.type(sets) == "array" then |
| 33 | + sets = { [DEFAULT_SET] = sets } |
| 34 | + end |
| 35 | + |
| 36 | + self.private.totalWeights = {} |
| 37 | + self.private.sets = {} |
| 38 | + |
| 39 | + for setName, set in pairs(sets) do |
| 40 | + assert(type(set) == "table" and lib.table.type(set) == "array", "Each set must be an array of OxSelectorItem") |
| 41 | + assert(#set > 0, "Each set must contain at least one OxSelectorItem") |
| 42 | + |
| 43 | + self.private.totalWeights[setName] = calculateTotalWeight(set) |
| 44 | + end |
| 45 | + |
| 46 | + self.private.sets = sets |
| 47 | +end |
| 48 | + |
| 49 | +--- Get a random non-weighted item from a specific set |
| 50 | +---@param setName? string |
| 51 | +---@return OxSelectorItem? |
| 52 | +function OxSelector:getRandom(setName) |
| 53 | + local set = (setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET] |
| 54 | + if not set then return nil end |
| 55 | + local item = set[math.random(#set)][2] |
| 56 | + |
| 57 | + return type(item) == "table" and deepClone(item) or item |
| 58 | +end |
| 59 | + |
| 60 | +--- Get a random weighted item from a specific set |
| 61 | +---@param setName? string |
| 62 | +---@return OxSelectorItem? |
| 63 | +function OxSelector:getRandomWeighted(setName) |
| 64 | + local set = (setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET] |
| 65 | + if not set then return nil end |
| 66 | + |
| 67 | + local totalWeight = self.private.totalWeights[setName or DEFAULT_SET] |
| 68 | + if totalWeight == 0 then return nil end |
| 69 | + |
| 70 | + local randomWeight = math.random() * totalWeight |
| 71 | + local cumulativeWeight = 0 |
| 72 | + |
| 73 | + for i = 1, #set do |
| 74 | + cumulativeWeight = cumulativeWeight + set[i][1] |
| 75 | + if randomWeight <= cumulativeWeight then |
| 76 | + local item = set[i][2] |
| 77 | + return type(item) == "table" and deepClone(item) or item |
| 78 | + end |
| 79 | + end |
| 80 | + |
| 81 | + return nil |
| 82 | +end |
| 83 | + |
| 84 | +--- get multiple non-weighted random items from a specific set |
| 85 | +---@param setName? string |
| 86 | +---@param count number |
| 87 | +---@return OxSelectorItem[] |
| 88 | +function OxSelector:getRandomAmount(setName, count) |
| 89 | + assert(type(count) == "number" and count > 0, "Count must be a positive number") |
| 90 | + local items = {} |
| 91 | + for _ = 1, count do |
| 92 | + local item = self:getRandom(setName) |
| 93 | + if item then |
| 94 | + table.insert(items, item) |
| 95 | + end |
| 96 | + end |
| 97 | + return items |
| 98 | +end |
| 99 | + |
| 100 | +--- get multiple weighted random items from a specific set |
| 101 | +---@param setName? string |
| 102 | +---@param count number |
| 103 | +---@return OxSelectorItem[] |
| 104 | +function OxSelector:getRandomWeightedAmount(setName, count) |
| 105 | + assert(type(count) == "number" and count > 0, "Count must be a positive number") |
| 106 | + local items = {} |
| 107 | + for _ = 1, count do |
| 108 | + local item = self:getRandomWeighted(setName) |
| 109 | + if item then |
| 110 | + table.insert(items, item) |
| 111 | + end |
| 112 | + end |
| 113 | + return items |
| 114 | +end |
| 115 | + |
| 116 | +--- get all items from a specific set |
| 117 | +---@param setName? string |
| 118 | +---@return OxSelectorItem[] |
| 119 | +function OxSelector:getSet(setName) |
| 120 | + return deepClone((setName and self.private.sets[setName]) or self.private.sets[DEFAULT_SET]) |
| 121 | +end |
| 122 | + |
| 123 | +--- get all sets |
| 124 | +---@return table<string, OxSelectorItem[]> |
| 125 | +function OxSelector:getAllSets() |
| 126 | + return deepClone(self.private.sets) |
| 127 | +end |
| 128 | + |
| 129 | +--- add a new set |
| 130 | +---@param setName string |
| 131 | +---@param items OxSelectorItem[] |
| 132 | +function OxSelector:addSet(setName, items) |
| 133 | + assert(type(setName) == "string", "setName must be a string") |
| 134 | + |
| 135 | + if self.private.sets[setName] then |
| 136 | + lib.print.error("Selector set '" .. setName .. "' already exists.") |
| 137 | + return |
| 138 | + end |
| 139 | + |
| 140 | + assert(type(items) == "table" and lib.table.type(items) == "array", "items must be an array") |
| 141 | + assert(#items > 0, "set must contain at least one OxSelectorItem") |
| 142 | + |
| 143 | + self.private.totalWeights[setName] = calculateTotalWeight(items) |
| 144 | + self.private.sets[setName] = items |
| 145 | +end |
| 146 | + |
| 147 | +--- update an existing set |
| 148 | +---@param setName string |
| 149 | +---@param newItems OxSelectorItem[] |
| 150 | +function OxSelector:updateSet(setName, newItems) |
| 151 | + assert(type(setName) == "string", "setName must be a string") |
| 152 | + |
| 153 | + if not self.private.sets[setName] then |
| 154 | + lib.print.error("Selector set '" .. setName .. "' does not exist.") |
| 155 | + return |
| 156 | + end |
| 157 | + |
| 158 | + assert(type(newItems) == "table" and lib.table.type(newItems) == "array", "newItems must be an array") |
| 159 | + assert(#newItems > 0, "set must contain at least one OxSelectorItem") |
| 160 | + |
| 161 | + self.private.totalWeights[setName] = calculateTotalWeight(newItems) |
| 162 | + self.private.sets[setName] = newItems |
| 163 | +end |
| 164 | + |
| 165 | +--- remove a set |
| 166 | +---@param setName string |
| 167 | +function OxSelector:removeSet(setName) |
| 168 | + assert(type(setName) == "string", "setName must be a string") |
| 169 | + |
| 170 | + self.private.totalWeights[setName] = nil |
| 171 | + self.private.sets[setName] = nil |
| 172 | +end |
| 173 | + |
| 174 | +lib.selector = OxSelector |
| 175 | +return lib.selector |
0 commit comments