I came up with a way to do it. It involves a few steps, but was rather easy overall.
Step 1) Download the attached zip file. It contains an Advance_Data.user file. (Contains no data that the editor can see). It also contains Advances_Configure_Hero.1st file. Place these in your savage folder. C:\ProgramData\Hero Lab\data\savage
This will allow for three optional rules: free attributes per rank, 25 xp per rank, and new skills that only cost 1/2 an advance instead of a full advance. My group hates spending a whole advance to get a d4 skill.
Step 2) Create your own user file with the following four things (3 mechanics and one Simple):
Mechanics: Free Attributes Per Rank, Half-Point New Skills, XP and Strict Rank Enforcement
Simple: Attribute Advance
Mechanic: Free Attributes Per Rank
Upon reaching each new Rank the character gains a point in an attribute. The free Novice attribute is at character creation, but one may also be taken as an Advance. That one is not free. You cannot mess with the existing attribute advance, but I was able to override it.
Upon gaining a new rank it will not add it to the character for you. However, with the XP table that I include in this coding it will not count a character as a given rank until enough advances have been taken. It even allows for a character to have negative XP (and stay as Novice for longer).
This has two Eval Scripts
Phase Pre-Traits
Priority 5000
Code:
~ If Attributes are free, this will mitigate the cost.
if (hero.tagis[source.FreeAtr] = 1) then
var bonus as number
bonus = 0
~ Check for Free Attribute Advances
foreach pick in hero from Advance sortas _CompSeq_
~ Account for having Free Attributes, as they are not to be counted for the total -- they are free, after all.
if (eachpick.tagis[AdvanceId.advAttriS] <> 0) then
bonus += 1
endif
if (eachpick.tagis[AdvanceId.advAttriV] <> 0) then
bonus += 1
endif
if (eachpick.tagis[AdvanceId.advAttriH] <> 0) then
bonus += 1
endif
if (eachpick.tagis[AdvanceId.advAttriL] <> 0) then
bonus += 1
endif
nexteach
~ Mitigates the cost so that it is free.
#resmax[resAdvance] += bonus
endif
Phase Setup
Priority 5000
Code:
~ The Free Novice atrribute is effectively bootstrapped during character creation.
if (hero.tagis[source.FreeAtr] = 1) then
#resmax[resAttrib] += 1
endif
Mechanic: Half-Point New Skills
By default rules, getting a new skill costs a full Advance. If "Half-Cost for New Skills" is checked it will only cost a half of an Adcance instead, as it is considered to be lower then the attribute.
One Eval script
Phase Pre-Traits
Priority 5000
Code:
~ If the Half-Cost New Skill is NOT checked
~ It is actually now the default.
if (hero.tagis[source.HalfSkill] = 0) then
var newSkill as number
newSkill = 0
~ Check for New Skill Advances
foreach pick in hero from Advance sortas _CompSeq_
if (eachpick.tagis[AdvanceId.advSkiLL] <> 0) then
newSkill += 0.5
endif
nexteach
~ Apply the total to the Advance Cost
perform #resspent[resAdvance,+,newSkill,"Full Advance New Skills"]
endif
Mechanic: XP and Strict Rank Enforcement
This mod changes the XP table to either 20 XP or 25 XP per rank. Additionally, Rank is determined by getting the advances and using them in order.
Novice 0
Seasoned 25 / 20
Veteran 50 / 40
Heroic 75 / 60
Legendary 100 / 80
Two eval scripts
Phase Final
Priority 5010
Code:
var endRank as number
var xpPerRank as number
var advPerRank as number
var advCount as number
var xp as number
var xpRank as number
var rankCount as number
var endRank as number
~ Determine XP table
if (hero.tagis[source.XPRank25] = 1) then
xpPerRank = 25
else
xpPerRank = 20
endif
advPerRank = xpPerRank / 5
advCount = 0
xp = hero.child[resXP].field[resMax].value
xpRank = xpPerRank
rankCount = 0
endRank = 0
~ Check for spent Advances
foreach pick in hero from Advance sortas _CompSeq_
advCount += eachpick.field[advCost].value
if (hero.tagis[source.FreeAtr] = 1) then
~ Account for having Free Attributes, as they are not to be counted for the total -- they are free, after all.
if (eachpick.tagis[AdvanceId.advAttriN] <> 0) then
rankCount += 1
endif
if (eachpick.tagis[AdvanceId.advAttriS] <> 0) then
rankCount += 1
endif
if (eachpick.tagis[AdvanceId.advAttriV] <> 0) then
rankCount += 1
endif
if (eachpick.tagis[AdvanceId.advAttriH] <> 0) then
rankCount += 1
endif
if (eachpick.tagis[AdvanceId.advAttriL] <> 0) then
rankCount += 1
endif
endif
nexteach
~ Determine Rank based on current Advance
~ Seasoned
rankCount -= 1
rankCount += advPerRank
if (advCount >= rankCount) then
if (xp >= xpRank) then
endRank = 1
endif
endif
~ Veteran
rankCount = rankCount + advPerRank
xpRank = xpRank + xpPerRank
if (advCount >= rankCount) then
if (xp >= xpRank) then
endRank = 2
endif
endif
~ Heroic
rankCount = rankCount + advPerRank
xpRank = xpRank + xpPerRank
if (advCount >= rankCount) then
if (xp >= xpRank) then
endRank = 3
endif
endif
~ Legendary
rankCount = rankCount + advPerRank
xpRank = xpRank + xpPerRank
if (advCount >= rankCount) then
if (xp >= xpRank) then
endRank = 4
endif
endif
~ Apply the Rank
herofield[acRank].value = endRank
Phase Pre-Traits
Priority 4000
Code:
~ Open additional advances on XP table change.
var xp as number
xp = hero.child[resXP].field[resMax].value
if (herofield[acCharType].value = -1) then
~ Determine XP table
if (hero.tagis[source.XPRank25] = 1) then
if (xp >= 85) then
#resmax[resAdvance] +=1
endif
if (xp >= 95) then
#resmax[resAdvance] +=1
endif
endif
endif
Simple: Attribute Advance
This simple will need to replace valAdvance (so put that in the "Replaces Thing ID" field in the bottom right.
Compares bonus Attributes with Advances.
Seasoned is at 5 Advances
Veteran is at 10 Advances
Heroic is at 15 Advances
Legendary is at 20 Advances, then may take one every other Advance (22, 24, 26, etc.).
This will have one Eval Script and one Eval Rule
Eval Script
Phase Validation
Priority 8000
Code:
var rank as number
var errorFound as number
var zCount as number
var zLimit as number
var xp as number
var xpTable as number
rank = herofield[acRank].value
errorFound = 0
xp = hero.child[resXP].field[resMax].value
~ Determine XP table
if (hero.tagis[source.XPRank25] = 1) then
xpTable = 100
else
xpTable = 80
endif
zCount = 0
zLimit = xp - xpTable
zLimit = round(zLimit / 20,0,-1)
if (xp <= xpTable) then
zLimit = 0
zCount = 0
endif
foreach pick in hero from Advance sortas _CompSeq_
if (eachpick.tagis[AdvanceId.advAttriS] <> 0) then
if (rank < 1) then
errorFound = 1
endif
endif
if (errorFound = 0) then
if (eachpick.tagis[AdvanceId.advAttriV] <> 0) then
if (rank < 2) then
errorFound = 1
endif
endif
if (errorFound = 0) then
if (eachpick.tagis[AdvanceId.advAttriH] <> 0) then
if (rank < 3) then
errorFound = 1
endif
endif
if (errorFound = 0) then
if (eachpick.tagis[AdvanceId.advAttriL] <> 0) then
if (rank < 4) then
errorFound = 1
endif
endif
if (eachpick.tagis[AdvanceId.advAttriZ] <> 0) then
if (rank < 4) then
errorFound = 1
endif
zCount += 1
endif
endif
endif
endif
if (zLimit < zCount) then
errorFound = 1
endif
if (errorFound = 1) then
perform hero.assign[Hero.MultiAttr]
done
endif
nexteach
if (errorFound = 1) then
perform hero.assign[Hero.MultiAttr]
endif
Eval Rule
Phase Validation
Priority 9000
Message An Attribute bonus was taken too early
Summary too early
Code:
~if we have no instances of multiple advances within a level, we're good
validif (hero.tagcount[Hero.MultiAttr] = 0)
~mark associated tab as invalid
container.panelvalid[advances] = 0
I know that you did not ask for the XP table mod or the skill point mod, but the XP table was tied into how the free attribute is designed, and the skill point mod needs to be part of that data file.