Please help: Trying to modify/fix Tome of Battle

It’s a fun mod, but there’s a few bugs that, well, bug me. I thought they’d be simple to locate but goddamn I’m beat.

Bug #1: Having the 'Keen Sense" feat makes all stances from Diamond Mind and Iron Heart to not appear at levelup. Similarly, the ‘Scent’ racial feat makes one tiger claw stance (correctly) not appear, but for some inexplicable reason White Raven stances also get removed.

I’m trying to check in the code where the heck it’s checking against these feats, I can only find two places, and neither should be causing this:

  • One for retraining feats, which checks if the player has the stance that grants scent, and then forbids retraining the granted scent feat.
  • One at levelup, which sounds like the right track… except there’s nothing there that would make Iron Heart or White Raven stances not appear, and the check on Keen Sense specifically checks for the one stance of DM that it should disable, not all of them.

Soooo, wth is happening here?

Bug #2: ToB gets screwed if you import/export characters. Importing such a character has them ‘forget’ all the ToB levelup info, making it think it has to do the maneuver learning from zero, but it doesn’t delete the maneuvers, resulting in way more maneuvers than the character should and impossible levelups when there’s just not enough unlearned maneuvers to pick (Crusader lvl 1, I’m looking at you).

As far as I understand the issue, the mod stores this data as local variables in the ‘ToB’ container item, and the maneuvers as items inside the container. Exporting/importing seems to nuke the data of the container (but not in the maneuvers).
Currently, I have a half-assed fix script that just nukes the maneuvers, allowing leveling up maneuvers from zero correctly. I’m not sure how I could fix this issue.

There’s, of course, other bugs, but these are the ones that are more in my face.

I did a quick test by commenting out this

else if ((n == STANCE_HEARING_THE_AIR) && (GetHasFeat(FEAT_KEEN_SENSE, oPC)))

check in bot9s_inc_levelup and that did seem to solve part of “bug#1”. But as far as I can read the code, it shouldn’t be causing that problem. Weird.

Yes! Staring intently at that bit of code now that you pointed out that it is, indeed, somehow at fault despite logic and sense saying otherwise, I found the damn bug!

nSpecial is never reset to 0.

So whenever a stance comes in and trips one of the checks for it, every maneuver after it gets hosed.

So all that’s needed to fix it is to add nSpecial = 0; in line 1260, and then recompile everything. Bug #1 is now solved :upside_down_face:.

1 Like

I’m honestly disappointed in myself for not spotting that. :expressionless: Glad that’s fixed.

As for Bug#2: I did some digging around in old notes and a quick test (sometimes I really miss the knowledge stored on the original Bioware NWN forums).
When a character is exported to the localvault, local variables on items in the characters’ inventory are removed. However, local variables stored on items, that are inside a container in the character’s inventory are not removed.
So a possible fix might be to store ToB’s variables on an item that’s placed in the “Book on Nine Swords” container.
But that’s a lot of code to rewrite. Perhaps your duct tape solution of nuking the maneuvers is the best way to handle it.

What are some other bugs you’ve noticed? It might be worth to put up a patch for the ToB pack on the vault.

2 Likes

For Bug #2, I thought about smartly applying the nuke if it detects the right conditions at levelup. Is there a way to check if an item has any variables? Because that’s the easy option, if the ToB item has stuff in it but doesn’t have any variables, then the character has been imported and it’s ToB item info was removed.

Because, yeah, the pretty solution of having an item inside the ToB item mirror the parent is troublesome. That’s a lot of ‘loop through maneuvers’ loops that would have to be modified, and it’d probably be pretty slow. A hack solution of putting characters in ‘export mode’ via cloning the ToB item into one inside and then removing it upon first levelup would be less of a mess.

Other bugs (so far):
Unlearning maneuvers does not unlearn them. So you basically get a new maneuver for free.

I’m noticing that ToB’s dialog.tlk file has a lot of sus incomplete entries that look like they’re for SoZ, either an earlier version of SoZ or the author tried some fancy merge, it screwed the file up, and they never noticed. If I hadn’t accidentally nuked my vanilla dialog.tlk file I’d make a diff comparison and fix that. Currently I have a maybe fix that’s merging with the one from Kaedrin + CCP/CPRC.

QoL:

  • ToB’s levelup UI does not adapt to higher resolutions. The most I’ve done with UIs is merge the popup menus so I’m not sure if making a custom UI that scales is possible.
  • ToB’s background images for maneuver descriptions and the maneuver selection menu are too high contrast, making the text hard to read. Probably easy fix by opening them on an image editor and lightening them up a tad.
  • ToB’s maneuver-selection menu always seem to bork the icon for the first maneuver selected, displaying a missing image instead of the correct one. Removing and adding the maneuver again fixes it, at least until you close the menu and open it again. I think there must be some screwed code there when the menu first opens that is starting from the wrong index or something.
    ToB’s floating class and quick maneuver menus don’t disappear when you switch characters, occupying a rather annoying amount of screen real estate. Ideally, they’d stay character-specific and just hide when you switch. Not sure how that’d be done, or if it’d be possible.

Not a bug but a compatibility thing:
Several things did not seem to be working yesterday, but they may be due to the module overriding something important. If I can track down what these have in common I’d be able to change the module and have it working:

  • Vital recovery, wasn’t recovering any
  • the first level shadow blade maneuver or whatever it’s called, it’s supposed to make a toggle button appear on the quick maneuver menu to switch modes, never did.
  • Counter charge

Maybe it’s the script running on the player? What was that one called again? Damn, it’s been too long since the last time I messed with NWN2.

Yes, you can make a custom UI that scales in the same way as the vanilla UIs. UIPane has a scalewithscene=“true” tag for that.
As for the UI’s background images being too high contrast. Yeah, Drammel did like his stuff to be flashy. I’ve actually started working on reworking ToB’s UI to be in the same style as the vanilla UI.

Ooh, nice.

As for me, I bit the bullet and did a quick reinstall. Now I’m manually going through all the different entries between ToB’s and SoZ’s TLK files. I’d love to have a tlk editor that calculated diffs and showed them side by side like you can with 2das, but at least good old tlkedit (the old one, not the java one) can list the differences.

Checking the ToB tlk file, and boy! this thing has been merged with a lot of stuff. Sadly, one or more of those mergers went badly and screwed over several strings, particularly a bunch of zalantar item descriptions.
Other than merges, there’s a lot of little changes of special characters to not-so-special characters. Which, on my end, does make a few text strings actually display those special characters ingame, so you can use this list of indexes to patch those into some other poor tlk file with killer tlkedit or something.

For posterity’s sake, here’s the changes:

ToB’s actual content:
292 - Cleave: add Stone Power as alternate preq
416 - Mobility: add Desert Wind Dodge as alternate preq
444 - WFocus: remove Fighter preq, because Warblade
2780 - Great Cleave: add Stone Power as alternate preq
2782 - Spring Attack: add Desert Wind Dodge as alternate preq
2812 - Divine Might: add Stone Power as alternate preq
2814 - Divine Shield: add Stone Power as alternate preq
6017 - Uncanny Dodge: add Warblade to description
6027 - Evasion: add Swordsage to description
6032 - Improved Evasion: add Swordsage to description
8598 - Great Smiting I: Adds Smite as alternate prereq
8783 - Extra Smiting: Adds Smite as alternate prereq
280000-282999 - ToB’s reserved tlk range where all the custom stuff is

Errors:
3038
7971
63455-63456 - Old ‘your key has expired, visit atari.com’, which my SoZ file changes to the GOG-appropriate text
137098
155799
161607
162084
176832
177903
178401
180942
205916
215845
216186
218438
232910
232938
232945
232952
232958
232962
232971

Charset Changes:
241
3114-3157
6075
13770
25203
53031
75941
75947
75949
75950
90857
90861
91117
91125
91129
91145
91179
91262
175593
178651
185809
185819

Misc:
225125 - Yuan-ti: add base spell DC to racial ability description
225127 - Yuan-ti: add base spell DC to racial ability description
225129 - Yuan-ti: add base spell DC to racial ability description
225130 - Yuan-ti: add base spell DC to racial ability description
225132 - Yuan-ti: add base spell DC to racial ability description

Some merges:

Stuff from CCP, incomplete
15000-15070

Voiceset stuff? Seems to add voice lines to a lot of combat shouts, from nwn1 stuff to nwn2 companions. Must be part of one of those ‘more pc voices’ mods.
3000
10487
75958-76378 nwn1 stuff?
177267-177311 Neeshka?

The SoZ tlk file ends at 234920, ToB extends all the way to 300161. There’s a lot of merged content in there, but ToB is supposed to be occupying the 280000-282999 tlk range, and as far as I can see on the 2das, it respects that range.

Here is two modified tlk files, a ‘clean’ file containing only the base SoZ one and ToB changes, and a ‘Errors Removed’ file that preserves all the charset and merging changes but fixes all those broken strings

dialogToB_Clean.7z (2.9 MB)
dialogToB_Errors_Removed.7z (3.1 MB)

I’m actually working on fixing a bunch of stuff in ToB at the moment. It does some really cool stuff - the combat override system is something that is super cool which we were not able to do in our ToB content for the PRC in NWN1 - but the code is a real mess. Look at the script for Avalanche of Blades if you want a good laugh! The brute force approach rather than using a simple loop in that one is heroic.

I will integrate that fix for nSpecial you posted about. As far as persistence between games goes, I did this for the PRC by running all the persistent ints through the associated SQL database. Not sure if NWN2 has that functionality though…

In NWN1 you can also export an object with all variables to a database, maybe this is possible in NWN2 as well. Like export the ToB with all variables to a database then have a script retrieve it when you press the button that gives it to you for the first time after loading the character into a new game. Since the ToB level up is handled through gui scripts, this would be doable if NWN2 has that functionality.

EDIT: perhaps using StoreCampaignObject() for ToB when the character levels up maneuvers, and then using RetrieveCampaignObject() when the character first clicks the button that grants the book initially. You would just need to give the item a unique variable name to recall it later. You would need to insert a new check into the menu_* scripts so it doesn’t just check for the book in the inventory, but also an integer that tells it you have/have not clicked the button before and tries to retrieve the book if you haven’t (even if you already have it in your inventory).

Other issues I’ve found and mostly fixed (greatest hits):

  • DR cannot be overcome with the scripted combat system as it can in NWN1 - I have it ignoring DR currently, but that is not a great fix
  • The author made it so that level-up maneuver availability isn’t calculated correctly based on IL. They did this to prevent saving up early maneuver picks until you are higher IL, but it makes it so that IL for level-up is only based on ToB class level and doesn’t add the 1/2 all other levels
  • The charge rules try and account for difficult terrain, but it just makes its so you can’t charge up/down even gentle hills
  • The system tries to establish rules to queue maneuvers, but it just causes a showstopper bug that prevents you from using maneuvers
  • The checks for Improved Uncanny Dodge and Sneak Attack apply in reverse, i.e. the uncanny dodger has to be 4 levels higher than the sneak attacker for the sneak attack to succeed
  • Cleave/Great Cleave is ignored
  • The extra attack from haste is ignored
  • Tactics of the Wolf ALWAYS gives the bonus to initiator even if not flanking, making it probably the best stance in the game

Placing variables on items stashed in other items worked in NWN1 before EE. This is how the PRC used to handle persistence. We would just force a token into the player’s skin and then set all the persistent variables on that. Beamdog broke/fixed that behavior though. If NWN2 does the same thing than it should work. It is, as you point out, a monumental task though. Searching the ToB scripts for “SetLocalInt(oToB” returned ~1000 results…

Where does this happen for you? Because I can’t seem to reproduce it.
I just did a quick test in both the OC and MotB, and the ToB menus properly disappeared when I switched from my Martial-PC to a companion.

They stay open for me too…

@ebonfowl:
About the GetLocalInt(“ToB”…, That part is easy. Just find & replace those with a custom function that does the job.

The issue is all the maneuver loops, they’re less, but you’d have to change them manually instead of relying on a single cast of find & replace magic.

Ok, now I’m puzzled.

I tested with clean override and vanilla campaigns… no issue. Makeovers? No issue. Same mod setup I had last time this exact problem cropped up? No issue.

Only think I can guess is that after a while running the script craps out and stops doing it, because I’m certain those menus weren’t anywhere this snappy in disappearing, sometimes not disappearing at all.


EDIT: also, the nSpecial fix also fixes the Crusader alignment maneuvers not appearing, which I’m rather happy about.
The ‘not retraining maneuvers’ issue: funny thing, in testing the gui disappearing thing I leveled a crusader all the way to 18. And at some point at higher levels, the retraining magically starts working and forgotten maneuvers are actually forgotten. Not sure what the hell is the trigger for this change in behavior.

Those GUI menus get recalled from the spell script associated with the Student of the Sublime Way feat. The script is called martial_adept. The one that controls switching characters is gui_tob_switch, maybe the issue is in one of those.

Do you have anything overriding partybar.xml? That is where the switcheroo is being called from.

EDIT: I do (TCHOS), so that explains why mine don’t switch on/off.

Find partybar.xml and then make the last portrait part look like this:

<!-- portrait -->
<UIButton name="PM_PORTRAIT" x=143 y=16 width=128 height=200 handleactiontarget=true 
OnLeftClick0=UIButton_Input_PossessCompanion("PARTY_BAR_SLOT",-1)
OnLeftClick1=UIObject_Misc_ExecuteServerScript("gui_tob_switch")

What you are changing are the two OnLeftClick parameters. They need to be 0 and 1. I tested, and that fixes any remaining issues.

This was taken from my modded xml, so it may look slightly different on your end. But the OnLeftClick parameters should be the same, and they are all that needs to be changed.

EDIT: there should really be an OnSelected parameter there if that button type will take one. As it is, it will only switch the menu when you click on the portrait, not if you switch characters by clicking on the character. I will add that to my list - I think it will require a new gui script as the switching logic will be slightly different as it doesn’t rely on a click.

In addition to that. Make sure the game isn’t paused when you switch characters.
Custom scripts in UIs don’t fire when the game is paused,

I have, but I did merge the change correctly and as I said earlier, with or without the other mods, the menu was magically working today and disappearing as it should.

Now part of that may be that I just started a campaign, rushed to having party members, and then tested the menu disappearing act.

Perhaps heavy use of cutscenes and adding/removing party members via script (including main pc) was the culprit. I’ll do some debugging next time it crops up. Or perhaps I was clicking on characters or using hotkeys and that’s what broke it.


Speaking of testing things, thanks for pointing out the gui_tob_switch script, because I figured a neat little hack:
If you remove the DelayCommand wrapper funciton on lines 101 and 106 and just call OpenScreens(oMartialAdept); and SwitchCharacters(); directly, the gui will now disappear while in pause, instead of sticking around whenever you pause the fight to swap to another character and cast some hell on some poor monsters.

Will this cause issues elsewhere? I have no idea :upside_down_face:! But the toggle button already did this instantly while in pause with no issues that I could see, so I don’t see how a script called from an xml doing the same would cause trouble.


Back on the "maneuver retraining doesn’t retrain, with a freshly-made crusader it seems to fail at lvl 4 but work at lvl 6. Presumably, it’ll keep working, so there may be something about lvl 4 or it being it’s first retraining that causes issues. Or perhaps it was caused by selecting to remove the same maneuver a second time.

That likely won’t cause a problem. The author just did it to preserve order. That is, close them first, then re-open if needed. All this should run through an OnSelected parameter in a radio button anyway and use different logic. There are so many checks, but I think all you really need is a check for the integer for each class (i.e. “SupernalClarity” for Warblade) and see whether it is saying the menu should be open or not.

Would you really need so many checks? Can’t you just use the existing OpenScreens script in gui_tob_switch ?

void OpenScreens(object oPC)
{
	object oToB = GetItemPossessedBy(oPC, "tob");
	int nCrusader = GetLevelByClass(CLASS_TYPE_CRUSADER, oPC);
	int nSaint = GetLevelByClass(CLASS_TYPE_SAINT, oPC);
	int nSwordsage = GetLevelByClass(CLASS_TYPE_SWORDSAGE, oPC);
	int nWarblade = GetLevelByClass(CLASS_TYPE_WARBLADE, oPC);

	if ((nCrusader > 0) && (GetLocalInt(oToB, "Unfettered") == 0))
	{
		DisplayGuiScreen(oPC, "SCREEN_MARTIAL_MENU_CR", FALSE, "martial_menu_cr.xml");
	}

	if ((nSaint > 0) && (GetLocalInt(oToB, "SublimeWay") == 0))
	{
		DisplayGuiScreen(oPC, "SCREEN_MARTIAL_MENU_SA", FALSE, "martial_menu_sa.xml");
	}

	if ((nSwordsage > 0) && (GetLocalInt(oToB, "DesertWind") == 0))
	{
		DisplayGuiScreen(oPC, "SCREEN_MARTIAL_MENU_SS", FALSE, "martial_menu_ss.xml");
	}

	if ((nWarblade > 0) && (GetLocalInt(oToB, "SupernalClarity") == 0))
	{
		DisplayGuiScreen(oPC, "SCREEN_MARTIAL_MENU_WB", FALSE, "martial_menu_wb.xml");
	}

	if ((GetHasFeat(FEAT_MARTIAL_STUDY, oPC)) && (GetLocalInt(oToB, "UmbralAwn") == 0))
	{
		DisplayGuiScreen(oPC, "SCREEN_MARTIAL_MENU", FALSE, "martial_menu.xml");
	}

	if (GetHasFeat(FEAT_MARTIAL_ADEPT, oPC))
	{
		DisplayGuiScreen(oPC, "SCREEN_SWIFT_ACTION", FALSE, "swift_action.xml");
	}
}

Lack of checking if oToB is a valid object aside, shouldn’t this work if called from an OnSelected? It checks if the character has the right class and opens the right gui, and it won’t do anything if it shouldn’t open the menu.

One big improvement I can think of is to also open the quickstrike menu if the character had it open, to match the behavior of quickcast and save the player another click.