From 041958c6a286b8cfce6824f66a8337585af8c183 Mon Sep 17 00:00:00 2001
From: Bunny <glisa825@gmail.com>
Date: Fri, 3 Apr 2020 01:17:04 -0400
Subject: [PATCH] Change how rare+ are generated to tree based instead of
 individual talent based

---
 game/modules/tome/class/GameState.lua | 166 +++++++++++++++++---------
 1 file changed, 111 insertions(+), 55 deletions(-)

diff --git a/game/modules/tome/class/GameState.lua b/game/modules/tome/class/GameState.lua
index ca08d3532c..c0b8c47d5b 100644
--- a/game/modules/tome/class/GameState.lua
+++ b/game/modules/tome/class/GameState.lua
@@ -2096,11 +2096,14 @@ function _M:applyRandomClass(b, data, instant)
 		end
 		if data.autolevel ~= false then b.autolevel = data.autolevel or "random_boss" end
 		
+		local tt_unlocked = {} -- unlocked categories by base 
+		local tt_locked = {} -- locked categories by base
 		-- Class talent categories
-		for tt, d in pairs(mclass.talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) end
-		for tt, d in pairs(mclass.unlockable_talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) end
-		for tt, d in pairs(class.talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) end
-		for tt, d in pairs(class.unlockable_talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) end
+		local ttypes = {}
+		for tt, d in pairs(mclass.talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) ttypes[tt] = table.clone(d) end
+		for tt, d in pairs(mclass.unlockable_talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) ttypes[tt] = table.clone(d) end
+		for tt, d in pairs(class.talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) ttypes[tt] = table.clone(d) end
+		for tt, d in pairs(class.unlockable_talents_types or {}) do b:learnTalentType(tt, true) b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d[2]) ttypes[tt] = table.clone(d) end
 
 		-- Non-class talent categories
 		if data.add_trees then
@@ -2109,9 +2112,11 @@ function _M:applyRandomClass(b, data, instant)
 					if type(d) ~= "number" then d = rng.range(1, 3)*0.1 end
 					b:learnTalentType(tt, true)
 					b:setTalentTypeMastery(tt, (b:getTalentTypeMastery(tt) or 1) + d)
+					ttypes[tt] = table.clone(d)
 				end
 			end
 		end
+
 		-- Add starting equipment
 		local apply_resolvers = function(k, resolver)
 			if type(resolver) == "table" and resolver.__resolver then
@@ -2158,76 +2163,127 @@ function _M:applyRandomClass(b, data, instant)
 			local t = b:getTalentFromId(tid)
 			if not t.no_npc_use and not t.no_npc_autolevel and (not t.random_boss_rarity or rng.chance(t.random_boss_rarity)) and not (t.rnd_boss_restrict and t.rnd_boss_restrict(b, t, data) ) then
 				local max = (t.points == 1) and 1 or math.ceil(t.points * 1.2)
-				local step = max / 50
+				local step = max / 70
 				tres[1][tid] = v + math.ceil(step * data.level)
 			end
 		end
 
-		-- Select additional talents from the class
-		local known_types = {}
-		for tt, d in pairs(b.talents_types) do
-			known_types[tt] = b:numberKnownTalent(tt)
+		-- learn talents based on trees: focus trees we take 3-4 talents from with a decent amount of point investment, shallow trees we only take 1 or 2 with not many points
+		-- ideally rares should feel different even within the same class based on what focus trees they get
+		local nb_focus, nb_shallow = 0, 0
+		local rank = b.rank
+		if rank <= 3.2 then 	--rare
+			nb_focus = math.floor(0.2 + rng.float(0.25, 0.35)*(math.max(0,data.level))^0.55) -- first around 8-10, second around 25-35, third around 50-70
+			nb_shallow = 2 + math.floor(0.25 + rng.float(0.1, 0.2)*(math.max(0,data.level))^0.6) -- third around 12-30, fourth 40-80
+		elseif rank >= 4 then 	--boss/elite boss 
+			nb_focus = 1 + math.floor(0.25 + rng.float(0.12, 0.33)*(math.max(0,data.level-4))^0.5) -- second around 10-16, third around 30-50, fourth around 60-85 uncommonly
+			nb_shallow = 1 + math.floor(0.33 + rng.float(0.1, 0.18)*(math.max(0,data.level-3))^0.6) -- second around 12-20, third 40-80
+		else 					--unique
+			nb_focus = 1 + math.floor(0.2 + rng.float(0.1, 0.3)*(math.max(0,data.level-10))^0.5) -- second around 17-30, third around 40-80 
+			nb_shallow = 1 + math.floor(0.7 + rng.float(0.1, 0.2)*(math.max(0,data.level-8))^0.6) -- second around 12-14, third around 30-70, fourth around 70-90 rarely
 		end
+		print("Adding "..nb_focus.." primary trees to boss")
+		print("Adding "..nb_shallow.." secondary trees to boss")
 
-		local list = {}
-		for _, t in pairs(b.talents_def) do
-			if b.talents_types[t.type[1]] then
+ 		local tt_choices = {}
+		for tt, d in pairs(ttypes) do
+			d.tt = tt
+			table.insert(tt_choices, d)
+		end
+		
+		local fails = 0
+		local focus_trees = {}
+		local shallow_trees = {}
+		local tt, tt_idx, tt_def
+		local t, t_idx
+		while #focus_trees < nb_focus or #shallow_trees < nb_shallow do
+			local ok = false
+			while not ok do
+				tt = rng.tableRemove(tt_choices)
+				if not tt or not tt.tt then break end
+				if not (tt.tt=="technique/combat-training" or tt.tt=="cunning/survival") then ok=true end
+			end
+			if not ok then break end
+			tt = tt.tt
+			tt_def = tt and b:knowTalentType(tt) and b.talents_types_def[tt]
+			if not tt_def then break end
+			print("[applyRandomClass] Attempting to add tree "..tt.." to boss")
+			local t_choices = {}
+			local nb_known = b:numberKnownTalent(tt)
+			for i, t in ipairs(tt_def.talents) do
+				local ok = true
 				if t.no_npc_use or t.not_on_random_boss then
-					known_types[t.type[1]] = known_types[t.type[1]] + 1 -- allows higher tier talents to be learnt
-				else
-					local ok = true
-					if t.rnd_boss_restrict and t.rnd_boss_restrict(b, t, data) then
+					nb_known = nb_known + 1 -- treat as known to allow later talents to be learned
+					print(" * Random boss forbade talent because talent not allowed on random bosses", t.name, t.id, data.level)
+					ok = false
+				end
+				if t.rnd_boss_restrict and t.rnd_boss_restrict(b, t, data) and ok then
+					print(" * Random boss forbade talent because of special talent restriction", t.name, t.id, data.level)
+					nb_known = nb_known + 1 -- treat as known to allow later talents to be learned
+					ok = false
+				end
+				if t.random_boss_rarity and rng.percent(t.random_boss_rarity) and ok then
+					print(" * Random boss forbade talent because of random boss rarity random chance", t.name, t.id, data.level)
+					nb_known = nb_known + 1 -- treat as known to allow later talents to be learned
+					ok = false
+				end
+				if data.check_talents_level and rawget(t, 'require') and ok then
+					local req = t.require
+					if type(req) == "function" then req = req(b, t) end
+					if req and req.level and util.getval(req.level, 1) > math.ceil(data.level) then
+						print(" * Random boss forbade talent because of level", t.name, t.id, data.level)
 						ok = false
-						print("[applyRandomClass] Random boss forbade talent because of special talent restriction", t.name, t.id, data.level)
 					end
-					if data.check_talents_level and rawget(t, 'require') then
-						local req = t.require
-						if type(req) == "function" then req = req(b, t) end
-						if req and req.level and util.getval(req.level, 1) > math.ceil(data.level/2) then
-							print("[applyRandomClass] Random boss forbade talent because of level", t.name, t.id, data.level)
-							ok = false
+				end
+				if ok then
+					table.insert(t_choices, t)
+				end
+			end
+			print(" Talent choices for "..tt..":")	for i, t in ipairs(t_choices) do print("\t", i, t.id, t.name) end
+			if #t_choices <= 2 or #focus_trees >= nb_focus then 
+				if #t_choices > 0 and #shallow_trees < nb_shallow then
+					table.insert(shallow_trees, tt) -- record that we added the tree as a minor tree
+					max_talents_from_tree = rng.percent(65) and 2 or 1
+					print("Adding secondary tree "..tt.." to boss with "..max_talents_from_tree.." talents")
+					for i = max_talents_from_tree,1,-1 do
+						local t = table.remove(t_choices, 1)
+						if not t then break end
+						local max = (t.points == 1) and 1 or math.ceil(t.points * 1.2)
+						local step = max / 60
+						local lev = math.max(1, math.round(rng.float(0.75,1.15)*math.ceil(step * data.level)))
+						print(#shallow_trees, " * talent:", t.id, lev)
+						if instant then -- affected by game difficulty settings
+							if b:getTalentLevelRaw(t) < lev then b:learnTalent(t.id, true, lev - b:getTalentLevelRaw(t.id)) end
+							if t.mode == "sustained" and data.auto_sustain then b:forceUseTalent(t, {ignore_energy=true}) end
+						else  -- applied when added to the level (unaffected by game difficulty settings)
+							b.learn_tids[t.id] = lev
 						end
 					end
-					if t.type[1]:find("/other$") then
-						print("[applyRandomClass] Random boss forbase talent because category /other", t.name, t.id, t.type[1])
-						ok = false
-					end
-					if ok then list[t.id] = true end
+				else 
+					print("Tree "..tt.." rejected")
 				end
-			end
-		end
-
-		local nb = 4 + 0.38*data.level^.75 -- = 11 at level 50
-		nb = math.max(rng.range(math.floor(nb * 0.7), math.ceil(nb * 1.3)), 1)
-		print("Adding "..nb.." random class talents to boss")
-
-		local count, fails = 0, 0
-		while count < nb do
-			local tid = rng.tableIndex(list, b.learn_tids)
-			if not tid or fails > nb * 5 then break end
-			local t = b:getTalentFromId(tid)
-			if t then
-				if t.type[2] and known_types[t.type[1]] < t.type[2] - 1 then -- not enough of talents of type
-					fails = fails + 1
-				else -- ok to add
-					count = count + 1
+			else
+				table.insert(focus_trees, tt) --record that we added the tree as a major tree
+				local max_talents_from_tree = rng.percent(75) and 4 or 3 
+				print("Adding primary tree "..tt.." to boss with "..max_talents_from_tree.." talents")
+				for i = max_talents_from_tree,1,-1 do
+					local t = table.remove(t_choices, 1)
+					if not t then break end
 					local max = (t.points == 1) and 1 or math.ceil(t.points * 1.2)
 					local step = max / 50
-					local lev = math.ceil(step * data.level)
-					print(count, " * talent:", tid, lev)
+					local lev = math.max(1, math.round(rng.float(0.75,1.25)*math.ceil(step * data.level)))
+					print(#focus_trees, " * talent:", t.id, lev)
 					if instant then -- affected by game difficulty settings
-						if b:getTalentLevelRaw(tid) < lev then b:learnTalent(tid, true, lev - b:getTalentLevelRaw(tid)) end
-						if t.mode == "sustained" and data.auto_sustain then b:forceUseTalent(tid, {ignore_energy=true}) end
+						if b:getTalentLevelRaw(t) < lev then b:learnTalent(t.id, true, lev - b:getTalentLevelRaw(t.id)) end
+						if t.mode == "sustained" and data.auto_sustain then b:forceUseTalent(t, {ignore_energy=true}) end
 					else  -- applied when added to the level (unaffected by game difficulty settings)
-						b.learn_tids[tid] = lev
+						b.learn_tids[t.id] = lev
 					end
-					known_types[t.type[1]] = known_types[t.type[1]] + 1
-					list[tid] = nil
 				end
-			else list[tid] = nil
 			end
-		end
-		print(" ** Finished adding", count, "of", nb, "random class talents")
+		end	
+		print(" ** Finished adding", #focus_trees, "of", nb_focus, "primary class trees") for i, tt in ipairs(focus_trees) do print("\t * ", tt) end
+		print(" ** Finished adding", #shallow_trees, "of", nb_shallow, "secondary class trees") for i, tt in ipairs(shallow_trees) do print("\t * ", tt) end
 
 		return true
 	end
-- 
GitLab