Aug 10, 2010

While we wait: What I have learned of the nwn2 toolset

The more I try to fix all the crap in the toolset or engine issues, the more I realize you will never fix it all, even if you waste that time. The thing about most of the bugs (primarily for my troubles and this example, initiating a conversation in a fail proof way) is that they are kind of low percentage random fails. You can't figure out why they fail so build these elaborate catches and exception scripts and then 3 people test it and it fails in two more ways.

For those who insist on using this broken product, of which there are several reasons to do so now that it has mature custom content, allow my last 3 months of hell to produce a lesson for the rest of you.

The secret cure: knowing that scripts and engine functions will work the majority of the time with random failures, place absolutely everything in question in a repetitious system. The one thing the engine has going for it is that everything has heartbeats. Using this to consistently try and re-try your intended action until it works, is the best fit way to seamless force something to work, even if a small delay gives a player pause, they will happily shrug it off and resume when it works six seconds later.

My Example:
Problem: Companion talks that occur on rest at the tavern

My approach: choose the conversation, assign it to the PC as talking to them self, and let the others join as their nodes come up

Issues:
-PC failing to talk because some action bumped it
-NPCs failing to say their line for whatever reason
-future conversations not making sense because prior ones failed at some point
-Companions /roster members going scripthidden permanently because the conversation failed and the call at the end to restore them can never fire

Fixes:

1 - The PC now, rather than being paralyzed, scripthidden etc etc in a vain attempt to get them to actually follow their action que and converse, simply is given a begin conversation repetitiously with no delay until the PC actually accepts it.

pseudo code:
do {BeginConversationWithSelf(PC) }
while (PCNotInConversation);

I noticed that this can call the script like 2000 times a second, but it works, and as soon as the PC actually accepts and is in the conversation, it stops, satisfied that whatever failed attempts may have occured, it has suceeded now.

I do not advance the conversation "counter" until in the final node of the talk, knowing that the conversation will have executed full at that point and the next rest-conversation is now allowed. This I had in long ago however, due to the random companion failures to speak that can be addressed by this method. I use a set int on the final node of the conversation, so its not directly in the script at all when it comes to advancing the rest talk counter.


2 - Fixing the "stuck invisible" roster members

While resting or leaving the area restored "in-party" members, if a conversation broke and everyone was invisible, the roster members (out of party companions) were gone forever.

I tried and wasted about 2 weeks trying to solve this, setting them to un-invis right before a rest talk (in case they were invised by the previous one) but this was still obviously a bug, I also set it to uninvis all members at the end of the conversation, as well as entering the tavern. While re-entering would re show the roster, in the end it was a bunch of work for what was still, a bug.

Final solution: in the taverns heartbeat script, if the PC is not in a conversation, then neither can his roster members be, there fore, set all roster members to VISIBLE if the PC is in the area and not conversing:

Pseduo-code for the tavern heartbeat script:

if (PCIsInTavern)
{ If (IsPCConversing (FALSE) ) SetAllRosterMembersVisible() };

This second method rather than using an infinite while loop simply fires every 6 seconds on the heart beat. By the time the player even notices they are gone they are already reappearing.

So really there is two options for a fail safe, either an infinite loop for instant trys and re-tries or the heartbeat wait, which is 6 seconds. Note that certain commands could cause an overflow error if using the infinite loop so I recommend the heartbeat whenever possible. The overflow error doesnt appear to be fatal though, it just means the script will fail, but if you are retrying it anyway it should resume the next try once the overflow is resolved.

I got this idea from looking at Obsidian's own way of making a conversation mandatory, via the CreateIPSpeaker() function. It simply spawns an IP speaker and keeps trying it every heart beat.

No weeks of frustration, no blocks of code for every possible failure. Just a clean, single line, that keeps knocking the door till it opens. This is ultimately the best and most efficient way to deal with something you know will randomly fail, and there is nothing you can do about it.

1 comment:

  1. Hi Eguintir,

    I hear you about engine issues. I also use plenty of checks and fall back to the heartbeat for some important variables. However, there is even one problem I get (very rarely though) that still needs looking at ... even with its current fallback check it can still fail! Thankfully it is the very first thing a player has to do, so I may simply write up a warning on the start of the game to help prepare for the problem.

    Other than that, I find I have resolved *most* (if not all) other problems related to scripts firing. Part of my own experience is that I test my code time and again between various updates and address issues as I find them. Sometimes nothing needs doing, but occassionally I get one or two scripts that need more careful attention, but once I iron th eproblem out, it's one less problem - and hopefully stable code to reuse at other times.

    Lance.

    ReplyDelete