Skip to content

Instantly share code, notes, and snippets.

@AzzaDeveloper
Last active August 1, 2024 04:16
Show Gist options
  • Save AzzaDeveloper/0bd2d718b659c5ab1cf9c60c0ee3d980 to your computer and use it in GitHub Desktop.
Save AzzaDeveloper/0bd2d718b659c5ab1cf9c60c0ee3d980 to your computer and use it in GitHub Desktop.
Shrine of order algorithm
local MAXIMUM_REDUCTION = 25
-- Table noting racial stats
local racialStats = {
Khan = {
Strength = 2,
Agility = 2,
},
}
-- This is the stat table that will be modified by Shrine of Order.
local startingStats = {
Strength = 52,
Fortitude = 90,
Agility = 30,
Intelligence = 12,
Willpower = 31,
Charisma = 60,
--
Flamecharm = 30,
Frostdraw = 0,
Thundercall = 35,
Galebreathe = 0,
Shadowcast = 0,
Ironsing = 0,
--
LightWeapon = 0,
MediumWeapon = 88,
HeavyWeapon = 0,
}
local attunements = { "Flamecharm", "Frostdraw", "Thundercall", "Galebreathe", "Shadowcast", "Ironsing" }
-- For this example, I'll assume that the player is a Khan
local playerStats = {
Race = "Khan",
PointsSpent = 0,
}
--
function order(stats)
-- Calculate the points spent so far in the build, not counting racials
for statName, v in stats do
if racialStats[playerStats.Race][statName] then
v -= racialStats[playerStats.Race][statName]
end
playerStats.PointsSpent += v
end
-- Save the points spent so far as point start
local pointsStart = playerStats.PointsSpent
-- Shallow copy the current stats as pre-shrine
local preshrineBuild = table.clone(stats)
-- Find out which stats are invested, so shrine knows to
-- reinvest into these stas.
local affectedStats = {}
for statName, statValue in stats do
-- Checks if this should be divided into
if statValue > 0 then
-- Check whether this is a stat affected by
-- racial stats.
if racialStats[playerStats.Race][statName] then
if racialStats[playerStats.Race][statName] > 0 then
if statValue - racialStats[playerStats.Race][statName] == 0 then
-- Skipping if stat - racialStat equals to 0 (have not invested)
continue
end
end
end
-- Add the affected stat to the affected stats table
table.insert(affectedStats, statName)
end
end
-- Initial division of points to every affected stats
for statName, _ in stats do
if table.find(affectedStats, statName) then
stats[statName] = pointsStart / #affectedStats
end
end
-- Prepare the bottlenecking step. For this algo,
-- bottlenecking refers to making sure stat reduction
-- is not larger than 25.
local bottleneckedDivideBy = #affectedStats
local bottlenecked = {}
local bottleneckedStats = false
local previousStats = table.clone(stats)
--
repeat
local bottleneckedPoints = 0
bottleneckedStats = false
for statName, statValue in stats do
-- Only bottleneck non-attunement affected stats
if not table.find(attunements, statName) and table.find(affectedStats, statName) then
local prevStat = previousStats[statName]
local shrineStat = preshrineBuild[statName]
if shrineStat - statValue > MAXIMUM_REDUCTION then
-- If the stat difference is larger than 25, set the stat to preshrine value - 25
-- This is refer to as the bottlenecked stat.
stats[statName] = shrineStat - MAXIMUM_REDUCTION
-- Add the difference to bottlenecked points, which will be used to redistribute to
-- other, non-bottenecked stats.
bottleneckedPoints += stats[statName] - prevStat
-- Add this stat a table of bottlencked stats.
table.insert(bottlenecked, statName)
-- Reduce the division counter by 1 (no longer take this stat into account)
bottleneckedDivideBy = bottleneckedDivideBy - 1
end
end
end
-- Averaging out bottlenecked points
for statName, statValue in stats do
if table.find(affectedStats, statName) and not table.find(bottlenecked, statName) then
-- Reduce from this stat the bottlenecked points divided
-- by the number of bottlenecked stats
-- Essentially, it takes away the same amount
-- from every stat to make up for the bottlenecked
-- stat.
stats[statName] = statValue - bottleneckedPoints / bottleneckedDivideBy
-- Check if after this step, any stat reduction is still
-- larger than 25.
if not table.find(attunements, statName) then
if preshrineBuild[statName] - statValue > 25 then
-- If so, set the bottlenecked stats to true, so the
-- loop will repeat.
bottleneckedStats = true
end
end
end
end
previousStats = table.clone(stats)
-- Repeat this step until no stat difference is larger than 25
until not bottleneckedStats
-- Round down all the stats.
-- The way this algorithm works makes sure that
-- all stats by the end is always equal, so we round down
-- and refund players the extra points.
for statName, statValue in stats do
stats[statName] = math.floor(statValue)
end
-- Calculate the points spent so far in the build after shrining
local pointsSpentAfterShrine = 0
for statName, v in stats do
if racialStats[playerStats.Race][statName] then
v -= racialStats[playerStats.Race][statName]
end
pointsSpentAfterShrine += v
end
-- Refund the points by subtracting the points spent after shrine
-- from the points spent before shrine.
local sparePoints = pointsStart - pointsSpentAfterShrine;
-- This step ensures that the spare points does not exceed the number of affected stats
if sparePoints > #affectedStats then
for _, statName in affectedStats do
stats[statName] = stats[statName] + 1;
end
end
return stats, sparePoints
end
-- This function returns the end stats and spare points.
print(order(startingStats))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment