Blue Circle of Death (BCOD) Investigation Thread

Discussion in 'General Modification' started by Gaear, Feb 1, 2010.

Remove all ads!
  1. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    The purpose of this thread is to investigate and document any fixes we can come up with for the Blue Circle of Death (BCOD) phenomenon. A BCOD is where an NPC leaves the party but his or her blue circle stays.

    What we know so far:

    • BCODs seem to be caused by an NPC's 'pc.follower_remove(npc)' command not being acknowledged, but its 'run_off(npc,pc)' command being acknowledged. In other words, the NPC doesn't leave the party, but they run off anyway.
    • The reason for a 'pc.follower_remove(npc)' command not being acknowledged is a mystery to me so far. In the cases I've examined, there is nothing wrong with the code, and different configurations can yield different results, and those results themselves are not always consistent.

    Case #1: Sargen

    Sargen is scripted to disband in Hommlet and Nulb.

    Sargen was reported to BCOD upon disbanding in Nulb.

    In testing, I found that Sargen did not BCOD in Nulb, but did BCOD in Hommlet. Here's his relevant dialog from Co8 5.7.2. In addition to 'pc.follower_remove(npc)' and 'run_off(npc,pc),' we can see that Sargen has another script in the mix, namely 'schedule_reward(npc,pc)' to set up the delivery of his reward:

    Code:
    {140}{I'm free! I'm free! Thank ye, demon! Thou hast kept thy word!}{I'm free! I'm free! Thank ye, demon! Thou hast kept thy word!}{}{140}{}{[B][COLOR="green"]schedule_reward(npc,pc)[/COLOR][/B]}
    {141}{You keep your word, Sargen. I want my gold.}{}{8}{}{150}{}
    {142}{Me want my gold!}{}{-7}{}{150}{}
    
    
    {150}{I'll send it off, demon! Thou hast my word! [He runs off]}{I'll send it off, demon! Thou hast my word! [He runs off]}{}{150}{}{[B][COLOR="red"]pc.follower_remove(npc)[/COLOR][/B]}
    {151}{Wait!}{}{1}{}{0}{[B][COLOR="red"]run_off(npc,pc)[/COLOR][/B]}
    After doing some checking around, I discovered that the configuration for NPCs disbanding and running off often goes like this instead:

    Code:
    {140}{I'm free! I'm free! Thank ye, demon! Thou hast kept thy word!}{I'm free! I'm free! Thank ye, demon! Thou hast kept thy word!}{}{140}{}{[B][COLOR="Red"]pc.follower_remove(npc)[/COLOR][/B]}
    {141}{You keep your word, Sargen. I want my gold.}{}{8}{}{150}{[COLOR="green"][B]schedule_reward(npc,pc)[/B][/COLOR]}
    {142}{Me want my gold!}{}{-7}{}{150}{[COLOR="green"][B]schedule_reward(npc,pc)[/B][/COLOR]}
    
    
    {150}{I'll send it off, demon! Thou hast my word! [He runs off]}{I'll send it off, demon! Thou hast my word! [He runs off]}{}{150}{}{[COLOR="red"][B]run_off(npc,pc)[/B][/COLOR]}
    {151}{Wait!}{}{1}{}{0}{}
    Namely, that 'pc.follower_remove(npc)' is used in the NPC's initial line of dialog, and 'run_off(npc,pc)' is used in his subsequent line of dialog. Neither is used in the PC's lines (where script commands occur, in the final set of brackets). So I changed Sargen to reflect this and moved his reward command to the PC's initial response line and voila, it works.

    But wait. The only problem is, the reward doesn't actually get scheduled (tested by waiting around for the prescribed time and then traveling on the worldmap - no dice). Now, to my knowledge you're supposed to be able to issue script commands from any final set of brackets in a dialog, not just the NPC's, so I have no idea why the other stuff wasn't working, let alone why it starts working if you put it in specific places, and let alone why it only works with another command in another place, and why that command doesn't subsequently work. :dizzy: It makes no frigging sense to me at all.

    I tried moving 'schedule_reward(npc,pc) around to a few other locations like so ...

    Code:
    {140}{I'm free! I'm free! Thank ye, demon! Thou hast kept thy word!}{I'm free! I'm free! Thank ye, demon! Thou hast kept thy word!}{}{140}{}{[B][COLOR="Red"]pc.follower_remove(npc)[/color][color="green"]; schedule_reward(npc,pc)[/color][/B]}
    {141}{You keep your word, Sargen. I want my gold.}{}{8}{}{150}{}
    {142}{Me want my gold!}{}{-7}{}{150}{}
    
    
    {150}{I'll send it off, demon! Thou hast my word! [He runs off]}{I'll send it off, demon! Thou hast my word! [He runs off]}{}{150}{}{[B][COLOR="red"][B]run_off(npc,pc)[/color][color="green"]; schedule_reward(npc,pc)[/B][/COLOR][/B]}
    {151}{Wait!}{}{1}{}{0}{[B][COLOR="Green"]schedule_reward(npc,pc)[/COLOR][/B]}
    but no dice. 'schedule_reward(npc,pc)' has to be in the PC's first response line for the disband and run_off scripts to work. If it is located somewhere else like in the first example, the reward works but the disband command doesn't and we get a BCOD. I also tried removing the 'schedule_reward' command altogether. Still no dice. It has to be there for the others to work.

    Even more stupid is the fact that other NPC's don't even have a 'schedule_reward' command in their disband scripts and they work properly. Just not Sargen.

    With the working example above, we'll see Sargen get removed from the party as he delivers his first line, and we'll see him fade away as he delivers his second line. I added Verbobonc to Sargen's disband script (because he probably should disband there too), and in Verbobonc, Sargen is immediately removed from the party and is gone the moment we set foot there, though he still delivers his lines ... with the same exact scripts and configurations that have him disbanding and fading away in sequence in Hommlet and Nulb.
     
    Last edited: Feb 2, 2010
  2. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    SOLVED!!!

    Apparently, bad code early can lead to bad results late. I found that an instance of what initially seems like it would be okay code but is actually bad code leads to all kinds of problems later on - conditionals no longer being acknowledged, flags and variables not being set upon command, etc. This bug is especially evil because it leaves no clues as to what is causing it.

    The bad code for Sargen was found earlier in his dlg file and had nothing to do directly with the disband and run off stuff, but rather with a check for paladins (see lines 25 and 26:

    Code:
    {20}{I, Sargen, beg thee for release from this hell!}{I, Sargen, beg thee for release from this hell!}{}{20}{}{}
    {21}{What do you mean? I'm not keeping you here!}{}{8}{pc.stat_level_get(stat_level_paladin) == 0}{30}{}
    {22}{Huh? Me not keep you here.}{}{-7}{pc.stat_level_get(stat_level_paladin) == 0}{30}{}
    {23}{And what do you offer in return, Sargen?}{}{8}{pc.stat_level_get(stat_level_paladin) == 0}{30}{}
    {24}{OK, but me want stuff, Sarge!}{}{-7}{pc.stat_level_get(stat_level_paladin) == 0}{30}{}
    {25}{No, because you are evil. If this is hell, you deserve to be here.}{}{8}{pc.stat_level_get(stat_level_paladin) [COLOR="Red"][B]>[/B][/COLOR] 0}{70}{}
    {26}{Me not think this is hell. But you are evil, so stay here anyway.}{}{-7}{pc.stat_level_get(stat_level_paladin) [B][COLOR="Red"]>[/COLOR][/B] 0}{70}{}
    Apparently ">" by itself or "<" by itself is bogus. When changed to ">= 1" instead, Sargen happily disbands and runs off in Hommlet, Nulb, and Verbobonc, and his scheduled reward also functions properly. Yay!

    Now to find any other instances of this bug ... (it's a Troika original btw). I initially discovered it while wracking my brains over why I couldn't get Bro Smyth to acknowledge or set a variable. He had a case of the infamous solo ">" early in his dialog too, sitting there under the guise of a friendly, smiling, harmless conditional but actually laying its evil plans for later with malice of forethought. (In fact, that's what caused the alignment checks not to be carried out on the masterwork question, which of course lead to dead end dialog.) F*ckin' b*tches!!
     
  3. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,613
    Likes Received:
    537
    That's the most bizarre thing I've ever heard.

    1. The '>' operator is supposed to be perfectly valid.
    2. When I test it in console (e.g. type 1>0), it returns correct results, without any errors.
    3. I tried adding the same Paladin check to Ostler's dialogue, seems like nothing breaks as a result. (e.g. Furnok quest worked fine)
    4. Brother Smith's dialogue seems to work fine...?
    5. I changed Sargen's conditionals to >= and still get BCOD when I travel the world map from Temple to Hommlet.
    So I'm afraid that's not it. Probably something far more sinister. Especially since teleporting via console to Hommlet doesn't seem to trigger BCOD.

    (this isn't a late april's fools joke, right?)

    Oh, btw, I think I've found a solution.
    Since the problem is that the runoff works and the disband doesn't, why not put both commands in the same place? Worst case scenario, the NPC won't disband at all.
    Tested it, seems to work, yay :)

    Edit:
    To clarify:
    Code:
    {151}{Wait!}{}{1}{}{0}{pc.follower_remove(npc); run_off(npc,pc)}
    
    Second edit:
    It seems that it doesn't work if you don't have some command in the NPC node.
    Code:
    {150}{I'll send it off, demon! Thou hast my word! [He runs off]}{I'll send it off, demon! Thou hast my word! [He runs off]}{}{150}{}{pc.follower_remove(npc)}
    {151}{Wait!}{}{1}{}{0}{pc.follower_remove(npc); run_off(npc,pc)}
    
    Note how I repeated the disband command.
    Further testing, it doesn't have to be a disband command in the NPC node for it to work, it can be any bogus command, doesn't even have to be a real function, e.g.
    Code:
    {150}{I'll send it off, demon! Thou hast my word! [He runs off]}{I'll send it off, demon! Thou hast my word! [He runs off]}{}{150}{}{boguss()}
    {151}{Wait!}{}{1}{}{0}{pc.follower_remove(npc); run_off(npc,pc)}
    
    The reward seems to work fine.

    In summary:
    Code:
    {140}{I'm free! I'm free! Thank ye, demon! Thou hast kept thy word!}{I'm free! I'm free! Thank ye, demon! Thou hast kept thy word!}{}{140}{}{[COLOR=SeaGreen]schedule_reward(npc,pc)[/COLOR]}
    {141}{You keep your word, Sargen. I want my gold.}{}{8}{}{150}{}
    {142}{Me want my gold!}{}{-7}{}{150}{}
    
    
    {150}{I'll send it off, demon! Thou hast my word! [He runs off]}{I'll send it off, demon! Thou hast my word! [He runs off]}{}{150}{}{[COLOR=SeaGreen]boguss()[/COLOR]}
    {151}{Wait!}{}{1}{}{0}{[COLOR=SeaGreen]pc.follower_remove(npc); run_off(npc,pc)[/COLOR]}
    Seems to work (knock on wood).
     
    Last edited: Apr 5, 2010
  4. Shiningted

    Shiningted I want my goat back Administrator

    Joined:
    Oct 23, 2004
    Messages:
    12,655
    Likes Received:
    352
    I really wouldn't do that. Go back and read what Gaear said again, Sitra:
    While '>' constituting 'bad code' may be debatable, bad scripts randomly breaking stuff further into the diaolgue file is undisputable. Its hard to test for because it seems to break things randomly (there is probably a logic to it that I aven't seen yet) but it definitely happens and can be a complete PITA to find later. I originally reported this in the 'Well Whaddya Know' thread <here> about 3 years ago and have seen plenty of examples of it since (in my own files).

    I realise you are advocating a bogus executable script rather than a bogus conditional, but I still wouldn't risk it. Better to put something legit in there - "x = 1", perhaps - and if that fixes the problem, great.
     
  5. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    < and > may not be 'bad code' strictly speaking, but changing it certainly fixed things in this instance. Also, if you search the dialog files for instances of < or > used by themselves, you won't find many.

    Anybody who's interested in testing this:

    Start a new game with unaltered 5.7.2. Go to Hommlet, then open the console and type

    Code:
    game.areas[3] = 1 (enter)
    game.areas[14] = 1 (enter)
    game.areas[9] = 1 (enter)
    This will mark Nulb, Verbobonc, and Hickory Branch on your Worldmap.

    Then type

    Code:
    from t import * (enter)
    t() (enter)
    This will give you a tough party, thanks to Sitra's t scripts file.

    Next go to Nulb, Verbobonc, and lastly Hickory Branch via the Worldmap. This will mark these places as visited so the description boxes don't pop up when you go there later for testing.

    Next, while still in Hickory Branch, open the console and type

    Code:
    create 14271 (enter)
    This will spawn Sargen, and he will start talking to you immediately. Let him join. Save your game.

    Next try traveling to Nulb, Hommlet, and Verbobonc, each time 'fresh' from the same save you just made, and observe what happens with Sargen.

    ~

    Now install the attached dialog file for Sargen (00166sargen) overtop the one in 5.7.2 (put it in data/dlg), and install the attached scripts file for Sargen (py00166sargen) overtop the one in 5.7.2 (put it in data/scr) and repeat all the above steps. Observe again what happens with sargen when you go to Hommlet, Nulb, and Verbobonc.


    I'm not real big on trying more theory when I'm quite sure the fix I came up with works, but if any brave volunteers can show that my fix isn't actually working the way I think it is, I'll gladly reconsider alternatives.
     

    Attached Files:

  6. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,613
    Likes Received:
    537
    1. I've tried the files.
    2. My experience has been that:
    3. They indeed solve the BCOD.
    4. It doesn't seem to be related to the '>' operator, because when I manually restore it, I don't get BCOD.
      Are you positive that when you switch >= to >, it reliably fails to work?
    5. Also, the timed event only works if you bring him to Hommlet; bringing him to Nulb and Verbobonc seems to prevent it from working.

    What concerns me is that the causes of BCOD may be varied, with each user experiencing it for different reasons and contexts.

    Regarding the bogus script:

    1. I originally created a script called bogus() in the py file with a "dummy = 1" script.
    2. It seemed to work when I changed the dialog command to boguss() as well.
    3. Certainly it doesn't hurt, and is better practice, to call a real function.
     
    Last edited: Apr 5, 2010
  7. Shiningted

    Shiningted I want my goat back Administrator

    Joined:
    Oct 23, 2004
    Messages:
    12,655
    Likes Received:
    352
    Well obviously if you created a function called 'bogus', then my objection is null ;) Carry on.
     
  8. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    I rather suspect that causes for BCODs are varied as well, but basically, since my working testbed install is what will eventually be transformed into 5.8.0, if it works for me it should work for everyone. :) (I realize that's not a reliable assumption across the board, but it's better than nothing or what we have right now, which seems broken.)

    Specifically, the Sargen dialog file I posted, minus the alteration of the > conditionals, was not relaibly functional and caused some BCODs (see the first post for details). Changing '> 0' to '>= 1' made it functional. I did not try changing them back.

    I suspect that results for cases where this sort of thing is happening are dependant upon whether the goofy dialog string has been encountered or not. So if the paladin check was not in a direct line to the disband entries, for example, you might disband and run off fine, but if it is, the disband stuff gets goofed up. :shrug:

    Regarding Bro Smyth, I had started adding dialog to accomodate the new expansion stuff, changing the original "this is a dull town" line to something generic just to point to a new node where all the different expansions could be delivered in sequence, and I basically couldn't get any condition in the new dialog to be acknowledged (would only fire if there were no conditions, and all I was checking for specifically was a var set at 0, which I verified was indeed 0 via console). When I changed the > to >= for a line in which you ask for directions to Nulb (node 20 I believe), the new conditionals suddenly started working. I think I had a vague recollection of Ted's assertions about bad code from earlier, and I was made suspicious of the > by the fact that going back to node 20 from my new node (I had an "ask more questions" redirect there) made one of the lines with conditionals (the one about being called brother) in the 20 node no longer available, even though the var for that was still set so that it should appear. The lone > in the Nulb line looked odd to me, so based on this I looked for other instances where the Nulb conditional (story state, I believe) was executed as simply ">" and found that that was the only instance. It seems it's always done as >/<= otherwise.

    I then remembered that Sargen had issues with what seemed to be unresponsive code, so I looked in his dlg file for single </>s, found the pally conditionals, changed them, and presto, everything worked.

    I don't pretend to understand why this is, but the bottom line is if I can get it work, I'll run with what works.

    I'll have to check the timed events for Verbo and Nulb myself and see what happens on my install.
     
  9. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    Okay, as far as rewards go, I get them reliably after Sargen disbands in Nulb, Verbobonc, and North Hommlet, but I don't get them in South or East Hommlet. That's odd, because I could swear it worked when I tested in South Hommlet initially, but meh.

    I added the reward script to sargen's new_map script where he is made to talk if you arrive in the three towns instead, and it still worked in Nulb, Verbo, and N Homm, but now he wouldn't run off in E and S Homm, and the reward wouldn't work either.

    [edit]

    I wonder ... we should consider scrapping rewards in their current form altogether and just script them to occur in towns instead. Tie them to other events or something like that to approximate the passage of time and just turn on the reward guy mobs (Welcome Wench? Boatman's Tavern?) when flag x goes boom.
     
    Last edited: Apr 7, 2010
  10. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    Case #2: Jaer

    Jaer was reported by TRC to BCOD in (you guessed it) S and E Hommlet, but to disband/run off okay in Nulb and N Hommlet. I verified the report.

    I added Verbobonc to his new_map script so that he runs off there as well.

    I changed his dialog as follows:

    Code:
    {140}{[Jaer turns to you] Thank you, @pcname@, for rescuing me from the Temple and those accursed nodes. You know I don't have much, but you can have my remaining fire balls. I hope they help you.}{[Jaer turns to you] Thank you, @pcname@, for rescuing me from the Temple and those accursed nodes. You know I don't have much, but you can have my remaining fire balls. I hope they help you.}{}{140}{}{[B][COLOR="Red"]pc.follower_remove(npc)[/COLOR][/B]}
    {141}{A:}{}{1}{}{150}{[COLOR="red"][B]create_item_in_inventory( 4197, pc ); create_item_in_inventory( 4197, pc ); create_item_in_inventory( 4197, pc ); create_item_in_inventory( 4197, pc ); create_item_in_inventory( 4197, pc ); create_item_in_inventory( 4197, pc ); create_item_in_inventory( 4197, pc ); create_item_in_inventory( 4197, pc )[/B][/COLOR]}
    
    
    {150}{Thanks again. I will never forget this act of kindness.}{Thanks again. I will never forget this act of kindness.}{}{150}{}{[COLOR="red"][B]run_off(npc,pc)[/B][/COLOR]}
    {151}{E:}{}{1}{}{0}{}
    to the 'typical' disband/run off scheme and now he works fine in all locations. He also gives up his fire balls in all locations. There were no other code changes, and no instances of < or > in his dialog.
     
    Last edited: Apr 8, 2010
  11. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    Case #3: Zaxis

    Zaxis was reported to BCOD if you gave him the note about his sister and then asked him to leave the group. I verified the BCOD.

    I changed Zaxis' dialog as follows:

    Code:
    {650}{No matter, I will go alone. First I will return home to tell my parents of Ima's demise. And then I shall find those responsible. They will pay.}{No matter, I will go alone. First I will return home to tell my parents of Ima's demise. And then I shall find those responsible. They will pay.}{}{}{}{[COLOR="Red"][B]pc.follower_remove(npc)[/B][/COLOR]}
    {651}{E:}{}{1}{}{0}{zaxis_runs_off2(npc,pc)}
    and now he disbands and runs off fine. Tested in several locations, but i don't think the locations matter in this instance because the zaxis_runs_off2 script simply didn't have the pc.follower_remove line (just attachee.runoff(attachee.location-3), which I added to his dialog before zaxis_runs_off2..
     
  12. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,613
    Likes Received:
    537
    There's no need to approximate anything - you can get an exact time reading via the game.time method. I've used it extensively in the Reactive Temple & Traders scripting.

    The gist:


    • game.time.time_game_in_seconds(game.time)
      • This command basically counts game time in seconds.
      • You can thus create Time Stamps when an event occurs.
      • If you wish for an event to transpire after a certain amount of time - script according to the time elapsed since the time stamp was made.
    • Working example -
      • see py00439script_daemon.py
      • Relevant functions:
      • Code:
        
        def record_time_stamp(tvar):
            if get_v(str(tvar)) == 0:
                set_v(str(tvar), game.time.time_game_in_seconds(game.time) )
            return
        
        
        
        def tsc( var1, var2 ):
        #time stamp compare
        #check if event associated with var1 happened before var2
        #if they happened in the same second, well... only so much I can do
            if (get_v(var1) == 0):
                return 0
            elif (get_v(var2) == 0):
                return 1
            elif (get_v(var1) < get_v(var2)):
                return 1
            else:
                return 0
        
        def tpsts(time_var, time_elapsed):
        # Has the time elapsed since [time stamp] greater than the specified amount?
            if get_v(time_var) == 0:
                return 0
            if game.time.time_game_in_seconds(game.time) > get_v(time_var) + time_elapsed:
                return 1
            return 0
        
        
        

    For more - see here. (Credit goes to Cerulean)
     
    Last edited: Apr 9, 2010
Our Host!