[Modding] Removing a class from a character

Hi fellow Forum dwellers,

I've been trying my hand at modding for a bit now (usually minor stuff, from minor gimmicks over moderate cheats up to flavor adjustments). There is however one thing that has me stumped: How to properly remove abilities from a character (and ideally refund the ability points).

 

The intended final result:
A book (item) that can be used to respec a hero / champion.

 

The thing that's always bothered me the most is finally reaching a high fame score (say: 800), getting a new champion and getting to choose between pest and cholera. Let's say a grossly misspecced Defender and a no less misspecced Commander. Yay.

I consider both classes at best borderline useful even in the best of cases. Both can be better replaced with troops (Defender) or an Eidolon (Street Building). However having to put up with misspecced versions of themselves is more than I really want to do after gathering those masses of fame. What a let-down. Given the options (either successfully petitioning Stardock to fix the basic problem or fixing myself a workaround) I figured doing a workaround would work best. Only I'm failing a bit ...

What I tried so far - since I couldn't figure out how to remove abilities - was to reduce the UnitStat_PathOfThe??? Attribute on the character, plus adding a Book that grants another class' ability. This worked reasonably well when trying to turn a Warrior into a Mage (He would still retain the warrior abilities, but would from then on level as Mage). It fails however when trying to turn an Assassin into anything else. I'd guess that's an alphabethical thing (haven't done thorough testing on the Alphabetical part - it's a dead end either way).

So ... anybody know a way on how to do this? Especially one where I can refund the points spent so far (and ONLY those already spent ...).

Cheers,
FWN

15,289 views 3 replies
Reply #2 Top

Hi Primal_Savage,

thanks for your swift reply. Yes, I am playing FE:LH and sorry about missing the modding subforum (I had simply been using the forum search to check whether someone else had a similar problem before posting).

Unfortunately, your proposed solution will not work for me, as it simply fixes a champion to a single specific class, while I desire for it to be a flexible tool.

So, after some further research I figured out a few things: First of all I got myself a full list of all attributes used in the GameModifier Xml Elements (There are a few more than offered in the Excel file)in all the 1891 official Xml files in the "English" folder. And yes: There is no attribute found that implies removing an ability.

Well ... if I can't change an existing unit, I guess I'll have to create a new Unit and remove the old one. As it happens, I found a way to create a new champion - I simple call the same attribute as the quest does, that grants you your fame-bases champions (RecruitFreeChampion). With value one that creates an unclassed champion, ready to imprinted upon. Which means I have to somehow remove the old Champion, now that I have created a new one. Dealing damage simply sends him to base with an injury. The PermanentDeath doesn't do a thing (whether alone or in combination with damage). Which left me with the StealSpirit attribute in order to get rid of him. Yay. So now I have a better steal spirit, but that's ok (I always choose the Decalon faction ability, so it's not too much of an advantage). On the other hand, the new Champion is Level 1, so it's still a trade-off (and the reason I made the spell cost no mana). Here's the spell:

Code: xml
  1. <Spells>
  2. <SpellDef InternalName="FWN_Spell_Reincarnate">
  3. <DisplayName>Reincarnate</DisplayName>
  4. <Description>Have a champion reincarnate into a different life (Warning: Destroys all Equipment)</Description>
  5. <Image>S_CloudWalk_Painting.png</Image>
  6. <IconFG>S_CloudWalk_Icon.png</IconFG>
  7. <AutoUnlock>1</AutoUnlock>
  8. <SpellBookSortCategory>Unit</SpellBookSortCategory>
  9. <SpellBookSortSubCategory>Other</SpellBookSortSubCategory>
  10. <SpellType>Strategic</SpellType>
  11. <SpellClass>Defensive</SpellClass>
  12. <SpellSubClass>Other</SpellSubClass>
  13. <SpellTargetType>FriendlyChampion</SpellTargetType>
  14. <CasterMustBeSov>1</CasterMustBeSov>
  15. <Prereq>
  16. <Type>AbilityBonusOption</Type>
  17. <Attribute>TheDecalon</Attribute>
  18. <Target>Player</Target>
  19. </Prereq>
  20. <SpellResourceCost>
  21. <Resource>Mana</Resource>
  22. <Amount>0</Amount>
  23. </SpellResourceCost>
  24. <GameModifier>
  25. <ModType>Unit</ModType>
  26. <Attribute>StealSpirit</Attribute>
  27. </GameModifier>
  28. <GameModifier InternalName="Reward1">
  29. <ModType>Player</ModType>
  30. <Attribute>RecruitFreeChampion</Attribute>
  31. <Value>1</Value>
  32. </GameModifier>
  33. <HitSoundFX>Spell_CloudWalk_01</HitSoundFX>
  34. <SpellDefEffect>
  35. <EffectName>S_CloudWalk_Particle</EffectName>
  36. <LocalPosition>0,0,0</LocalPosition>
  37. <EffectScale>0.50</EffectScale>
  38. <EffectDelay>0</EffectDelay>
  39. <SnapToTerrain>1</SnapToTerrain>
  40. </SpellDefEffect>
  41. </SpellDef>
  42. </Spells>

Cheers,
FWN

Ps.:

Since I did some automation in my search (no way I'd check close to 1900 files manually), I built myself a couple of powershell functions to do this for me. In case you care, here they are:

Code: php
  1. Function Get-XmlElement
  2. {
  3. <#
  4. .SYNOPSIS
  5. Searches Xml Documents for elements of the given Name.
  6. .DESCRIPTION
  7. Searches Xml Documents for elements of the given Name.
  8. The search is performed recursively.
  9. This function can be passed either XmlDocument objects, or FileInfo objects that aim at Xml files.
  10. .PARAMETER Document
  11. The XmlDocument to be searched for ChildElements of the given Name.
  12. .PARAMETER File
  13. The Xml File that is to be searched for ChildElements of the given Name.
  14. .PARAMETER Name
  15. The Name of the Element to be searched for.
  16. .EXAMPLE
  17. PS C:\> dir "C:\temp" -recurse | Where {$_.Extension -eq ".xml"} | Get-XmlElement -Name "GameModifier"
  18. This will search the folder "C:\temp" and all its child folders for Xml files, then search each such file for Elements named "GameModifier" and return these Elements.
  19. #>
  20. [CmdletBinding()]
  21. Param (
  22. [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "Document")]
  23. [System.Xml.XmlDocument]
  24. $Document,
  25. [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = "File")]
  26. [System.IO.FileInfo]
  27. $File,
  28. [Parameter(Mandatory = $true)]
  29. [String]
  30. $Name
  31. )
  32. Begin
  33. {
  34. # Store the calling function's name
  35. $ParSet = $CmdletBinding.ParameterSetName
  36. # Store the counter that measures the amount of child-nodes searched
  37. $script:iterations = 0
  38. #region Recursive Search Function
  39. Function Get-MemberNode
  40. {
  41. <#
  42. .SYNOPSIS
  43. Function that searches all children nodes recursively, until either a match or dead end is reached
  44. #>
  45. Param (
  46. $Node
  47. )
  48. $script:iterations++
  49. $Props = $Node | Get-Member -MemberType 'Property'
  50. $Results = @()
  51. foreach ($p in $Props)
  52. {
  53. $type = ($p.Definition -split " " | Select -First 1)
  54. $pname = $p.Name
  55. if (($p.Name -eq $Name) -and ($type -eq "System.Xml.XmlElement")) { $Results += $Node.$pname }
  56. elseif (($p.Name -eq $Name) -and ($type -eq "System.Object[]")) { $Results += $Node.$pname }
  57. elseif ($type -eq "System.Object[]") { $Node.$pname | %{ $Results += Get-MemberNode $_ } }
  58. elseif ($type -eq "System.Xml.XmlElement") { $Results += Get-MemberNode $Node.$pname }
  59. }
  60. Return $Results
  61. }
  62. #endregion Recursive Search Function
  63. }
  64. Process
  65. {
  66. if ($ParSet -eq "Document")
  67. {
  68. foreach ($Doc in $Document)
  69. {
  70. Get-MemberNode $Doc
  71. }
  72. }
  73. else
  74. {
  75. foreach ($f in $File)
  76. {
  77. $Xml = $null
  78. [Xml]$Xml = Get-Content $f.FullName
  79. Get-MemberNode $xml
  80. }
  81. }
  82. }
  83. End
  84. {
  85. # Report the number of nodes searched
  86. Write-Verbose "Elements searched: $Iterations"
  87. }
  88. }

... and ...

Code: php
  1. Function Write-XmlDocument
  2. {
  3. <#
  4. .SYNOPSIS
  5. Combines a number of Xml Elements into a new Xml Document
  6. .DESCRIPTION
  7. Combines a number of Xml Elements into a new Xml Document
  8. .PARAMETER Element
  9. The Elements to join into one document.
  10. .PARAMETER XmlStructure
  11. The Basic XmlStructure of the Document. Example:
  12. if set to @("Configuration","Computer") the document would look like this:
  13. <Configuration>
  14. <Computer>
  15. <Element1>
  16. ...
  17. </Element1>
  18. <Element2>
  19. ...
  20. </Element2>
  21. ...
  22. <ElementN>
  23. ...
  24. </ElementN>
  25. </Computer>
  26. </Configuration>
  27. .PARAMETER Path
  28. The full path, including Filename, that the Document shall be written to.
  29. The containing Folder needs to exist, however the file needs not.
  30. .PARAMETER Encoding
  31. Default: UTF8
  32. The encoding of the Xml File
  33. .EXAMPLE
  34. PS C:\> $Elements | Write-XmlDocument -XmlStructure @("AbilityBonuses) -Path "C:\Temp\NewAbilityBonuses"
  35. This will create a new Xml Document with all Elements stored beneath the root Element <AbilityBonuses>
  36. #>
  37. [CmdletBinding()]
  38. Param (
  39. [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
  40. [System.Xml.XmlElement[]]
  41. $Element,
  42. [Parameter(Mandatory = $true)]
  43. [String[]]
  44. $XmlStructure,
  45. [Parameter(Mandatory = $true)]
  46. [String]
  47. $Path,
  48. [System.Text.Encoding]
  49. $Encoding = [System.Text.Encoding]::UTF8
  50. )
  51. Begin
  52. {
  53. try
  54. {
  55. # Create XmlWriter
  56. [System.Xml.XmlTextWriter]$Writer = New-Object System.Xml.XmlTextWriter($Path, $Encoding)
  57. # Configure Xml Writer
  58. $Writer.Indentation = 4
  59. $Writer.Formatting = [System.Xml.Formatting]::Indented
  60. # Create Xml Structure
  61. $XmlStructure | %{ $Writer.WriteStartElement($_) }
  62. # Create Counter
  63. $Count = 0
  64. }
  65. catch
  66. {
  67. throw (New-Object System.InvalidOperationException("An Error occured while initializing the new Xml Document: $($_.Exception.Message)", $_.Exception))
  68. }
  69. }
  70. Process
  71. {
  72. foreach ($e in $Element)
  73. {
  74. $e.WriteTo($Writer)
  75. $Count++
  76. }
  77. }
  78. End
  79. {
  80. # Commit Xml to file
  81. $Writer.Flush()
  82. # Report results
  83. Write-Verbose "Operation finished, $Count Elements written to $Path"
  84. }
  85. }

They were built with Powershell 3.0+ in mind (Windows 8+), but may work with version 2 (Windows 7) as well. Usage may require some commandline skills (But neither edits existing files, except the single file specified in Write-XmlDocument, so they are safe to use).

PPs.: The Codeblock header may be saying it's PHP. It's not. PHP simply comes closest to Powershell in terms of formatting.

Reply #3 Top

Quoting FWN, reply 2

Unfortunately, your proposed solution will not work for me, as it simply fixes a champion to a single specific class, while I desire for it to be a flexible tool.

 

You don't need to insert a new path at all. I inserted one because I thought you wanted to switch Commanders/Defenders for something else. If you leave the Path ability blank a new path will be selected upon leveling and you will get to keep the equipment, the levels, and the other abilities. If you change the other abilities to be general abilities rather than specialized abilities (That you would found in specialized paths), then you will have 100% unspecialized champions with the correct number of abilities (and equipment) for their level.

 

In any case, thanks for sharing your solution and if it fits your needs, go with it  :thumbsup: .

 

BTW, You can modify the forum this is posted in if you edit the OP, should be a "Category" options at the top of your post.

 

 

 

_________________________

LH Mods by Primal

XtraDeconstruct

XtraDeconstruct Canons