Skip to content

Commit db0bd16

Browse files
Feat: Selector (#59)
* selector init * fixes * recalculate set weights on add/update
1 parent 01935ad commit db0bd16

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed

imports/selector/shared.lua

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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

Comments
 (0)