From bfffa178c3b257830cd43e24901f35fbcce24a21 Mon Sep 17 00:00:00 2001
From: Hachem_Muche <Hachem_Muche@stanfordalumni.org>
Date: Wed, 12 Apr 2017 11:48:06 -0700
Subject: [PATCH] AdvanceActor dialog: Stat values are split into base and
 bonus values, and base (maximum allowed by level) and bonus stat values
 (estimated, +40 at level 50) are automatically updated when the character
 level is updated. Check boxes enable/disable character level and stat values.

By default, the actor (player) level is set to 50 and all stats are forced to 60 base, 40 bonus (100 Total), matching standard scaling limits.
---
 .../tome/dialogs/debug/AdvanceActor.lua       | 170 +++++++++++++-----
 1 file changed, 121 insertions(+), 49 deletions(-)

diff --git a/game/modules/tome/dialogs/debug/AdvanceActor.lua b/game/modules/tome/dialogs/debug/AdvanceActor.lua
index e3325807a6..8ef0881356 100644
--- a/game/modules/tome/dialogs/debug/AdvanceActor.lua
+++ b/game/modules/tome/dialogs/debug/AdvanceActor.lua
@@ -37,16 +37,35 @@ function _M:init(data)
 	
 	Dialog.init(self, ("DEBUG -- Levelup Actor: [%s] %s"):format(self.actor.uid, self.actor.name), 800, 500)
 
-	self.inputs = {}
+	-- set default inputs
+	self.inputs = {do_level_up=true, levelup=50, set_base_stats=true, set_bonus_stats=true}
+	
 	self.c_tut = Textzone.new{auto_width=true, auto_height=true, no_color_bleed=true, font=self.font,
 	text=[[Levelup an actor.
 Optionally set Stat levels, learn all talents possible, and gain points to spend on Levelup. 
 The actor is backed up before changes are made.  (Use the "Restore" button to recover.)
 ]]}
 	local top = self.c_tut.h + 10
-	local lev_box = Numberbox.new{title="Advance to level: ", number=self.actor.level or 50, max=1000, min=1, chars=10, fct=function(value)
+	local do_level_up = Checkbox.new{title="", text="Text", default=self.inputs.do_level_up, check_last=false,
+		fct=function(checked)
+		end,
+		on_change=function(checked)
+			self.inputs.do_level_up = checked
+			if not checked then
+				self.lev_box:setText(tostring(self.actor.level))
+			end
+		end
+	}
+	self.do_level_up = do_level_up
+	
+	local lev_box = Numberbox.new{title=" Advance to Level: ", number=self.inputs.levelup, max=1000, min=1, chars=6, fct=function(value)
 			self.inputs.levelup = value
 			self:finish()
+		end,
+		on_change = function(value)
+			self.inputs.levelup = value
+			self.base_stat_box:autovalue(self)
+			self.bonus_stat_box:autovalue(self)
 		end
 	}
 	self.lev_box = lev_box
@@ -63,15 +82,13 @@ The actor is backed up before changes are made.  (Use the "Restore" button to re
 	local restore = Button.new{text=rest_text, alpha_unfocus=rest_alpha,
 		fct=function()
 			local last_actor = _M.last_actors[self.actor]
-			
 			if last_actor and #last_actor > 0 then
 				local uid = self.actor.uid
 				local la = table.remove(last_actor, #last_actor)
 				game.log("#LIGHT_BLUE#Restoring [%s]%s from backup version %d", self.actor.uid, la.name, #last_actor+1)
 				self.actor:replaceWith(la)
 				self.actor.uid = uid
-				self.lev_box.number = self.actor.level
-				self.lev_box:updateText(0)
+				self.lev_box:setText(tostring(self.actor.level))
 			end
 			self.restore.text, self.restore.alpha_unfocus = restore_text(self)
 			self.restore:generate() -- force redraw
@@ -80,7 +97,7 @@ The actor is backed up before changes are made.  (Use the "Restore" button to re
 	}
 	self.restore = restore
 	
-	local get_points = Checkbox.new{title="Set unlimited respec and gain points for stats, talents, and prodigies ", text="Text", default=false, check_last=false,
+	local get_points = Checkbox.new{title="Gain points for stats, talents, and prodigies (unlimited respec)", text="Text", default=false, check_last=false,
 		fct=function(checked)
 		end,
 		on_change=function(checked)
@@ -89,30 +106,56 @@ The actor is backed up before changes are made.  (Use the "Restore" button to re
 	}
 	self.get_points = get_points
 	
-	local set_statlvl = Checkbox.new{title=" Change Stats ", default=false, check_last=true,
+	local set_base_stats = Checkbox.new{title="", default=self.inputs.set_base_stats, check_last=false,
 		fct=function(checked)
-			self.inputs.levelup_stats = checked
+			self.inputs.set_base_stats = checked
 		end,
 		on_change=function(checked)
-			self.inputs.levelup_stats = checked
+			self.inputs.set_base_stats = checked
 		end
 	}
-	self.set_statlvl = set_statlvl
-	local stat_box = Numberbox.new{title="Force all stats to: ", number="maximum for level", max=1000, min=1, chars=20, fct=function(value)
-			self.stat_box:updateText(0)
-			self.inputs.stat_levelup = value
-			self:finish()
+	self.set_base_stats = set_base_stats
+	
+	local base_stat_box = Numberbox.new{title=" Force all BASE stats to: ", number=self:autoStatLevel(self.inputs.levelup), max=1000, min=1, chars=6, fct=function(value)
+			self.inputs.base_stat_level = value
 		end,
 		on_change = function(value)
-			if not self.stat_box.inputted then value = 100 self.stat_box.inputted = true end
-			if not self.set_statlvl.checked then self.set_statlvl:select() end
-			self.stat_box.number = value
-			self.stat_box:updateText(0)
-			self.inputs.stat_levelup = value
+			if not self.set_base_stats.checked then self.set_base_stats:select() end
+			self.inputs.base_stat_level = value
 		end
 	}
-	self.stat_box = stat_box
+	base_stat_box.autovalue = function(self, dialog)
+		if dialog.inputs.set_base_stats then
+			self:setText(tostring(dialog:autoStatLevel(dialog.inputs.levelup)))
+		end
+	end
+	self.base_stat_box = base_stat_box
 
+	local set_bonus_stats = Checkbox.new{title="", default=self.inputs.set_bonus_stats, check_last=false,
+		fct=function(checked)
+			self.inputs.set_bonus_stats = checked
+		end,
+		on_change=function(checked)
+			self.inputs.set_bonus_stats = checked
+		end
+	}
+	self.set_bonus_stats = set_bonus_stats
+	
+	local bonus_stat_box = Numberbox.new{title=" Force all BONUS stats to: ", number=self:autoStatBonusLevel(self.inputs.levelup), max=1000, min=-50, chars=6, fct=function(value)
+			self.inputs.bonus_stat_level = value
+		end,
+		on_change = function(value)
+			if not self.set_bonus_stats.checked then self.set_bonus_stats:select() end
+			self.inputs.bonus_stat_level = value
+		end,
+	}
+	bonus_stat_box.autovalue = function(self, dialog)
+		if dialog.inputs.set_bonus_stats then
+			self:setText(tostring(dialog:autoStatBonusLevel(dialog.inputs.levelup)))
+		end
+	end
+	self.bonus_stat_box = bonus_stat_box
+	
 	local set_tl = Checkbox.new{title="Learn Talents ", text="Text", default=false, check_last=true,
 		fct=function(checked)
 		end,
@@ -177,19 +220,22 @@ The actor is backed up before changes are made.  (Use the "Restore" button to re
 	local ok = Button.new{text="Accept", fct=function() self:finish() end}
 	local cancel = Button.new{text="Cancel", fct=function() self:cancelclick() end}
 
-	local top = lev_box.h + get_points.h + self.c_tut.h + 15
+	local top = lev_box.h + self.c_tut.h + 15
 	self:loadUI{
 		{left=10, top=0, padding_h=10, ui=self.c_tut},
-		{left=10, top=self.c_tut.h+5, padding_h=10, ui=lev_box},
-		{left=10, top=self.c_tut.h+lev_box.h+10, padding_h=10, ui=get_points},
+		{left=10, top=self.c_tut.h+5, padding_h=10, ui=do_level_up},
+		{left=do_level_up.w+10, top=self.c_tut.h+5, padding_h=10, ui=lev_box},
+		{left=do_level_up.w+lev_box.w+20, top=self.c_tut.h+5, padding_h=10, ui=get_points},
 		{right=10, top=0, padding_h=10, ui=restore},
-		{left=10, top=top, padding_h=10, ui=set_statlvl},
-		{left=set_statlvl.w+20, top=top, padding_h=10, ui=stat_box},
-		{left=10, top=top+stat_box.h+5, padding_h=10, ui=set_tl},
-		{left=set_tl.w+20, top=top+stat_box.h+5, padding_h=10, ui=tl_box},
-		{right=10, top=top+stat_box.h+5, padding_h=10, ui=force_tl},
-		{left=10, top=top+stat_box.h+tl_box.h+10, padding_h=10, ui=mastery_box},
-		{right=10, top=top+stat_box.h+tl_box.h+10, padding_h=10, ui=learn_all_talents},
+		{left=10, top=top, padding_h=10, ui=set_base_stats},
+		{left=set_base_stats.w+10, top=top, padding_h=10, ui=base_stat_box},
+		{left=10, top=top+set_base_stats.h+5 , padding_h=10, ui=set_bonus_stats},
+		{left=set_bonus_stats.w+10, top=top+set_base_stats.h+5, padding_h=10, ui=bonus_stat_box},
+		{left=10, top=top+base_stat_box.h+bonus_stat_box.h+10, padding_h=10, ui=set_tl},
+		{left=set_tl.w+20, top=top+base_stat_box.h+bonus_stat_box.h+10, padding_h=10, ui=tl_box},
+		{right=10, top=top+base_stat_box.h+bonus_stat_box.h+10, padding_h=10, ui=force_tl},
+		{left=10, top=top+base_stat_box.h+bonus_stat_box.h+tl_box.h+15, padding_h=10, ui=mastery_box},
+		{right=10, top=top+base_stat_box.h+bonus_stat_box.h+tl_box.h+15, padding_h=10, ui=learn_all_talents},
 		{left=10, bottom=0, ui=ok},
 		{right=10, bottom=0, ui=cancel},
 	}
@@ -213,13 +259,23 @@ end
 
 -- Levelup the actor
 function _M:finish()
-	self.inputs.levelup = self.inputs.levelup or tonumber(self.lev_box.number)
---	print("[ForceLevelUp] inputs:", self.inputs) table.print(self.inputs, '\t_inputs_')
+	self.inputs.levelup = self.inputs.do_level_up and (self.inputs.levelup or tonumber(self.lev_box.number)) or self.actor.level
+	print("[ForceLevelUp] inputs:", self.inputs) table.print(self.inputs, '\t_inputs_')
+	
+-- debugging
+	local inpts={}
+	for i, v in pairs(self.inputs) do
+		inpts[#inpts+1] = ("%s = %s"):format(i, v)
+	end
+	game.log("#LIGHT_BLUE#AdvanceActor inputs: %s", table.concatNice(inpts, ", "))
+-- debugging
 	game:unregisterDialog(self)
+	
 	local data = table.clone(self.inputs)
-	data.stat_level = self.inputs.levelup_stats and (self.inputs.stat_levelup or self:autoStatLevel(self.inputs.levelup))
+	data.base_stat_level = self.inputs.set_base_stats and (self.inputs.base_stat_level or self:autoStatLevel(self.inputs.levelup))
+	data.bonus_stat_level = self.inputs.set_bonus_stats and (self.inputs.bonus_stat_level or self:autoStatBonusLevel(self.inputs.levelup))
 	data.talent_level = self.inputs.levelup_talents and (self.inputs.talent_levelup or self:autoTalentLevel(self.inputs.levelup))
-	self:levelupActor(self.actor, self.inputs.levelup, data)
+	self:levelupActor(self.actor, self.inputs.do_level_up and self.inputs.levelup, data)
 end
 
 function _M:cancelclick()
@@ -230,25 +286,32 @@ function _M:autoTalentLevel(charlev)
 	return 5 + math.max(0, math.floor((charlev - 50) / 10))
 end
 
+--- returns maximum stat levels vs. character as allowed by dialogs.CharacterSheet.lua
 function _M:autoStatLevel(charlev)
-	return math.min(charlev*1.4 + 20, 60 + math.max(0, charlev - 50))
+	return math.floor(math.min(charlev*1.4 + 20, 60 + math.max(0, charlev - 50)))
+end
+
+--- returns estimated stat bonuses vs. character level )+40 @ level 50 with diminishing returns)
+function _M:autoStatBonusLevel(charlev)
+	return math.round(math.min(40*((charlev-1)/50)^.5 + 1, charlev*0.8))
 end
 
---- Levelup Actor, possibly increasing stats, and learning talents
+--- Levelup Actor, possibly increasing stats and learning talents
 -- who = actor to level up
 -- lev = target character level
 -- data = table of optional levelup parameters:
--- 		stat_level: adjust stat bonuses to force all primary stats to this value
+-- 		base_stat_level: force all primary base stats to this value
+-- 		bonus_stat_level: force all primary stats bonuses to this value
 -- 		talent_level: learn all possible talent to this (raw) level
 --		ignore_talent_limits: ignore restrictions when levelling up talents
 -- 		alltalents: unlock all talent types in the game (before learning talents)
--- 		set_mastery: force mastery level for all talent types
+-- 		set_mastery: force mastery level for all known talent types
 function _M:levelupActor(who, lev, data)
 	who = who or game.player
 	 -- backup the character
 	_M.last_actors[who] = _M.last_actors[who] or {}
 	table.insert(_M.last_actors[who], who:cloneFull())
-	game.log("#LIGHT_BLUE#Advancing actor %s[%s]", who.name, who.uid)
+--	game.log("#LIGHT_BLUE#Advancing actor %s[%s]", who.name, who.uid)
 	local tt_def=who.talents_types_def
 	who.lastLearntTalentsMax = function(what) return 500 end
 	
@@ -259,20 +322,20 @@ function _M:levelupActor(who, lev, data)
 	end
 	lev = who.level
 
-	game.logPlayer(who, "#LIGHT_BLUE#Level %d: Setting primary stats to %s, maximum talent levels to %s", lev, data.stat_level, data.talent_level)
-	if data.stat_level then
-		game.logPlayer(who, "#GOLD#Forcing all Stats to %s", data.stat_level)
+	if data.base_stat_level then
+		game.log("%s #GOLD#Forcing all Base Stats to %s", who.name, data.base_stat_level)
 		for stat = 1, 6 do
-			local inc = data.stat_level - who:getStat(stat)
-			who:incIncStat(stat, inc)
+			local inc = data.base_stat_level - who:getStat(stat, nil, nil, true)
+			who:incStat(stat, inc)
 		end
 	end
-	if data.set_mastery then game.logPlayer(who, "#GOLD#Resetting all talents_types_mastery to %s", data.set_mastery) end
+	
+	if data.set_mastery then game.log("%s #GOLD#Resetting all talents_types_mastery to %s", who.name, data.set_mastery) end
 	if data.alltalents then
-		game.logPlayer(who, "#GOLD#Unlocking All Talent Types")
+		game.log("%s #GOLD#Unlocking All Talent Types", who.name)
 		for key, value in ipairs(tt_def) do
 			who:learnTalentType(tt_def[key].type)
-			game.logPlayer(who, "#LIGHT_BLUE#%s -- %s",key, value.type)
+			game.log("#LIGHT_BLUE#%s -- %s", key, value.type)
 		end
 	end
 	for tt, _ in pairs(who.talents_types) do
@@ -281,7 +344,7 @@ function _M:levelupActor(who, lev, data)
 			who:setTalentTypeMastery(tt, data.set_mastery)
 		end
 		if ttd and data.talent_level then
-			game.logPlayer(who, "#GOLD#Checking %s Talents (%s)", tt, who:getTalentTypeMastery(tt))
+			game.log("#GOLD#Checking %s Talents (%s)", tt, who:getTalentTypeMastery(tt))
 			who:learnTalentType(tt, true)
 			for i, t in pairs(ttd.talents) do
 				if not (t.is_object_use or t.is_inscription or t.uber) then
@@ -294,12 +357,21 @@ function _M:levelupActor(who, lev, data)
 							else break
 							end
 						end
-						if learned then game.logPlayer(who, "#LIGHT_BLUE#Talent %s learned to level %d", t.id, who:getTalentLevelRaw(t)) end
+						if learned then game.log("#LIGHT_BLUE#Talent %s learned to level %d", t.id, who:getTalentLevelRaw(t)) end
 					end
 				end
 			end
 		end
 	end
+	
+	-- done last to reverse any passive stat bonuses from talents
+	if data.bonus_stat_level then
+		game.log("%s #GOLD#Forcing all Bonus Stats to %s", who.name, data.bonus_stat_level)
+		for stat = 1, 6 do
+			local inc = data.bonus_stat_level - (who:getStat(stat, nil, nil, false) - who:getStat(stat, nil, nil, true))
+			who:incIncStat(stat, inc)
+		end
+	end
 	if data.get_points then
 		who:attr("infinite_respec", 1)
 		game.state.birth.ignore_prodigies_special_reqs = true
-- 
GitLab