253 lines
7.0 KiB
Lua
253 lines
7.0 KiB
Lua
--
|
|
-- {{{ Digit Functions
|
|
--- Get sorted base-10 digits of a number
|
|
---@param n integer
|
|
---@return integer[]
|
|
local function get_sorted_digits(n)
|
|
local digits = {}
|
|
for d in tostring(math.abs(n)):gmatch("%d") do
|
|
table.insert(digits, tonumber(d))
|
|
end
|
|
table.sort(digits)
|
|
return digits
|
|
end
|
|
-- }}}
|
|
|
|
--{{{ print_r
|
|
--- @diagnostic disable-next-line unused-function
|
|
local function print_r(t)
|
|
local print_r_cache = {}
|
|
--- @diagnostic disable-next-line unused-function
|
|
local function sub_print_r(t, indent)
|
|
if (print_r_cache[tostring(t)]) then
|
|
print(indent .. "*" .. tostring(t))
|
|
else
|
|
print_r_cache[tostring(t)] = true
|
|
if (type(t) == "table") then
|
|
local tLen = #t
|
|
for i = 1, tLen do
|
|
local val = t[i]
|
|
if (type(val) == "table") then
|
|
print(indent .. "#[" .. i .. "] => " .. tostring(t) .. " {")
|
|
sub_print_r(val, indent .. string.rep(" ", string.len(i) + 8))
|
|
print(indent .. string.rep(" ", string.len(i) + 6) .. "}")
|
|
elseif (type(val) == "string") then
|
|
print(indent .. "#[" .. i .. '] => "' .. val .. '"')
|
|
else
|
|
print(indent .. "#[" .. i .. "] => " .. tostring(val))
|
|
end
|
|
end
|
|
for pos, val in pairs(t) do
|
|
if type(pos) ~= "number" or math.floor(pos) ~= pos or (pos < 1 or pos > tLen) then
|
|
if (type(val) == "table") then
|
|
print(indent .. "[" .. pos .. "] => " .. tostring(t) .. " {")
|
|
sub_print_r(val, indent .. string.rep(" ", string.len(pos) + 8))
|
|
print(indent .. string.rep(" ", string.len(pos) + 6) .. "}")
|
|
elseif (type(val) == "string") then
|
|
print(indent .. "[" .. pos .. '] => "' .. val .. '"')
|
|
else
|
|
print(indent .. "[" .. pos .. "] => " .. tostring(val))
|
|
end
|
|
end
|
|
end
|
|
else
|
|
print(indent .. tostring(t))
|
|
end
|
|
end
|
|
end
|
|
|
|
if (type(t) == "table") then
|
|
print(tostring(t) .. " {")
|
|
sub_print_r(t, " ")
|
|
print("}")
|
|
else
|
|
sub_print_r(t, " ")
|
|
end
|
|
|
|
print()
|
|
end
|
|
--}}}
|
|
|
|
--{{{ table_count
|
|
--- @diagnostic disable-next-line unused-function
|
|
local function table_count(t)
|
|
local count = 0
|
|
for _ in pairs(t) do count = count + 1 end
|
|
return count
|
|
end
|
|
|
|
--}}}
|
|
|
|
--{{{ range(low, high, n)
|
|
---Create a table with integers in a given range
|
|
---@param low integer Lowest number
|
|
---@param high integer Highest number
|
|
---@param n integer Increment - the array will contain every nth number between [low..high]
|
|
---@return table
|
|
local function range(low, high, n)
|
|
local t = {}
|
|
for i = low, high, n do
|
|
table.insert(t, i)
|
|
end
|
|
|
|
return t
|
|
end
|
|
---}}}
|
|
|
|
--{{{ sortMapByValueDesc(m)
|
|
---Sort the table m by value.
|
|
---@param m table
|
|
---@return table
|
|
local function sortMapByValueDesc(m)
|
|
local totalCount = 0
|
|
local tmp = {}
|
|
|
|
-- create a temporary table that is easier to sort
|
|
for damage, occurrences in pairs(m) do
|
|
local entry = {
|
|
damage = damage,
|
|
count = occurrences,
|
|
}
|
|
totalCount = totalCount + occurrences
|
|
table.insert(tmp, entry)
|
|
end
|
|
|
|
table.sort(tmp, function(a, b)
|
|
return a.count > b.count
|
|
end)
|
|
|
|
return tmp
|
|
|
|
--
|
|
-- local result = {}
|
|
-- for _, v in pairs(tmp) do
|
|
-- result[v.damage] = v.count
|
|
-- end
|
|
--
|
|
--
|
|
-- if table_count(result) ~= table_count(m) then
|
|
-- error("I couldn't sort!!")
|
|
-- end
|
|
--
|
|
-- return result
|
|
end
|
|
--}}}
|
|
|
|
--{{{table_sum(t)
|
|
---numeric sum of table values
|
|
---@param t integer[]
|
|
---@return integer
|
|
local function table_sum(t)
|
|
local result = 0
|
|
for _, v in pairs(t) do
|
|
result = result + v
|
|
end
|
|
|
|
return result
|
|
end
|
|
--}}}
|
|
|
|
--{{{mostCommonDamageValues(instances, ratio)
|
|
--- Calculate the most likely values
|
|
---@param instances table A map of [damage score] => [occurrences]
|
|
---@param ratio number Include most common damage until the sum of the probabilities of the damage scores are at least this number.
|
|
---@return table
|
|
local function mostCommonDamageValues(instances, ratio)
|
|
if ratio == nil then
|
|
ratio = 0.5
|
|
end
|
|
|
|
local sortedInstances = sortMapByValueDesc(instances)
|
|
local totalOccurrences = table_sum(instances)
|
|
|
|
local result = {}
|
|
local occurrenceAccumulator = 0
|
|
|
|
for _, instance in pairs(sortedInstances) do
|
|
occurrenceAccumulator = occurrenceAccumulator + instance.count
|
|
table.insert(result, instance.damage)
|
|
|
|
if occurrenceAccumulator / totalOccurrences >= ratio then
|
|
return result
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
--}}}
|
|
|
|
--{{{calculateDamageD100(skill, base_damage)
|
|
---Calculate the average of the d100 attack method
|
|
---@param skill integer The skill level of the attacker (range of 1 to 100, but will be capped to 20)
|
|
---@param base_damage integer The base damage of the attacker's weapon.
|
|
---@return number
|
|
---@return table mostRolled the damage values that are most likely to be rolled
|
|
local function calculateDamageD100(skill, base_damage)
|
|
local sum = 0
|
|
local n = 0
|
|
local tn = math.min(skill, 80)
|
|
local damages = {}
|
|
|
|
for rolled = 1, 100, 1 do
|
|
local digits = get_sorted_digits(rolled)
|
|
|
|
local damage = 0
|
|
|
|
if rolled > tn then
|
|
-- glancing blow: damage equal to weapon's base damage.
|
|
damage = base_damage
|
|
end
|
|
if rolled < tn then
|
|
-- normal hit: damage equal digit sum of what is rolled plus base damage.
|
|
damage = table_sum(digits) + base_damage
|
|
end
|
|
if rolled == tn then
|
|
-- Critical Hit: damage equal to what you rolled.
|
|
damage = rolled
|
|
end
|
|
|
|
local key = tostring(damage)
|
|
n = n + 1
|
|
sum = sum + damage
|
|
if not damages[key] then
|
|
damages[key] = 1
|
|
else
|
|
damages[key] = damages[key] + 1
|
|
end
|
|
end
|
|
|
|
return sum / n, mostCommonDamageValues(damages, 0.5)
|
|
end
|
|
--}}}
|
|
|
|
local skill_min = 30
|
|
local skill_max = 80
|
|
local skill_inc = 5
|
|
local skills_table = range(skill_min, skill_max, skill_inc)
|
|
|
|
local damage_min = 0
|
|
local damage_max = 10
|
|
local damage_inc = 3
|
|
local damages_table = range(damage_min, damage_max, damage_inc)
|
|
|
|
io.write("skill\\damage", ";")
|
|
for i, dmg in pairs(damages_table) do
|
|
io.write(string.format("%d (avg);%d (typical)", dmg, dmg))
|
|
if i < #damages_table then
|
|
io.write(";")
|
|
end
|
|
end
|
|
print("")
|
|
|
|
|
|
for _, skill in pairs(skills_table) do
|
|
io.write(skill, ";")
|
|
|
|
local tmp = {}
|
|
for key, damage in ipairs(damages_table) do
|
|
local avg, dmg = calculateDamageD100(skill, damage)
|
|
tmp[key] = string.format("%.2f;%s", avg, table.concat(dmg, ", "))
|
|
end
|
|
print(table.concat(tmp, ";"))
|
|
end
|