User:Sunjammer/Plot flags (draft)

From Dragon Age Toolset Wiki
Jump to: navigation, search

There are many categories of plot but fortunately only two types of plot flags:

  • Main flags
  • Defined flags

Main Flags

A Main Flag is a bit like NWN Plot Entry only with a few added features and fields. Just as with their NWN equivalent, they can be either "Not set" or "Set". They have the following properties/fields:

  • Flag Name
  • Flag
  • Reward
  • Has Journal
  • Final
  • Repeatable
  • Default Value
  • Area Location Tag
  • Offer ID
  • Journal Text
  • Plot Assist
  • Comments

You can set the Flag Name to whatever you like, however you'll want to get your naming conventions sorted out early on and stick to them like glue (it will pay dividends when it comes to using them). The Flag is a incrementing and unique integer. If you choose a reward the Reward field displays the label of the row from the rewards.2da*. Entering anything in to Journal Text will cause Has Journal to change from nothing to "Yes". Final, Repeatable and Default Value should be self explanatory.

The significance of Area Location Tag, Offer Id and Plot Assist remain a mystery to me.

You can have a total of 128 Main Flags (numbered 0 to 127) in any single plot.

Defined Flags

On the face of it Defined Flags are much simpler than Main Flags. These too can be either "Not set" or "Set" but have fewer properties/fields, namely:

  • Flag Name
  • Flag
  • Reward
  • Area Location Tag
  • Offer ID
  • Plot Assist
  • Comments

You can have a seemingly unlimited number of Defined Flags (numbered from 256 upwards). However after adding about 800 Defined Flags to a single plot the Plot Editor began to run a little slow.

I said "on the face of it" because as I was about to find out Defined Flags are evil and twisted ...

Little Red Riding Hood Redux

In Little Red Riding Hood Redux (our Builders Event module) you had to collect some food, drink and flowers before Grandmother would allow the plot to progress. I, naively, thought I could achieve this by setting up three Defined Flags (one for each item) and setting them using the equivalent of the Module's OnAcquireItem event.

The submission deadline was fast approaching when I threw together a module event script that looked a lot like this:

    #include "wrappers_h" 
    #include "events_h"    
    #include "plt_no_good_deed"
 
    void main()
    {
        int bEventHandled;
        event evEvent = GetCurrentEvent();
        int nEventType = GetEventType(evEvent);
        switch(nEventType)
        {      
            case EVENT_TYPE_CAMPAIGN_ITEM_ACQUIRED:
            {   
                // the item's owner is the creature who triggered this event
                object oOwner = GetEventCreator(evEvent); 
 
                // the item is the first entry in the event's objects list
                object oItem = GetEventObject(evEvent, 0);
                string sItemTag = GetTag(oItem);
 
                // check the tag for special cases and raise appropriate defined flag
                if(sItemTag == "food")
                { 
                    WR_SetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_FOOD, TRUE);
                }
                else if(sItemTag == "drink")
                { 
                    WR_SetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_DRINK, TRUE);
                }
                else if(sItemTag == "flower")
                { 
                    WR_SetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_FLOWER, TRUE);
                }  
 
                // check if all defined flags have been raised: and raise main flag
                if(WR_GetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_FOOD)
                && WR_GetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_DRINK)
                && WR_GetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_FLOWER))
                {
                    WR_SetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_ALL_ITEMS, TRUE);
                }
 
                bEventHandled = TRUE;
                break;
            }
        } 
 
        // fall through logic
        if(bEventHandled == FALSE)
        {
            HandleEvent(evEvent, RESOURCE_SCRIPT_MODULE_CORE);
        }
    }

It looks sound enough: each time a plot item is acquired we set the appropriate Defined Flag. If all three Defined Flags are set then we set the Main Flag which controls the a node in Grandmother's conversation.

Unfortunately we didn't have time to test it and so we were somewhat confused and a little chagrined with it our demonstration ended a little prematurely with Grandmother refusing to say much more than "Yes deary?"

To understand why we have to know a little bit more about the differences between a Main Flag and a Defined Flag.

Setting (and Checking) Expectations

What we didn't know at the Builders Event was that although the state Main Flag can be checked and/or updated, the state of a Defined Flag can only be checked. My attempts to update the Defined Flags in the above script were always doomed to fail.

In addition, while a Main Flag can be checked and/or updated just using without resorting to scripting (by merely selecting options on a conversation's "Plots and Scripting" tab), checking a Defined Flag will always involve a call to the appropriate plot script ... and we hadn't written one of those!

With Defined Flags there's simply no getting away from scripting.

To check the state either type of flag through scripting we can use the native GetPartyPlotFlag function or the derived WR_GetPlotFlag function. To update the state of a Main Flag we can use the native SetPartyPlotFlag function or the derived WR_SetPlotFlag function. Normally Main flags are changed from "Not set" to "Set" (0 to 1) but the reverse is possible too.

Schroedinger's Flag?

It took me a while to get my head round Defined Flags mostly because I find referring to these as "flags" rather unhelpful. Calling them flags makes me think they have an intrinsic state and can be set or unset just like the Main Flags. Unfortunately this is not the case.

So if it isn't really a flag what is it?

   Quote: by Craig Graff (BioWare)
   I tend to think of it as ... peering in on Schroedinger's cat

In reality a Defined Flag is just an integer constant that is used to identify which snippet of code to run in a plot script. The snippet of code is scripted to return a true/false value just as StartingConditional script were in NWN. The difference here is that you would normally have several Defined Flags in a single plot script.

Little Red Riding Hood Redux Redux?

So if I had to remake LRRHR again what would I need to do to make it work? There are a couple of options however at this stage I can't really say which is better.

Option A

In this option we change PLAYER_HAS_FOOD, PLAYER_HAS_DRINK and PLAYER_HAS_FLOWER from Defined Flags to Main Flags. These are just temporary flags so we don't give them any Journal Entry, Reward, etc. In this way they become settable so we can leave the part of the original script that checks tags and updates flags.

Conversely we leave PLAYER_HAS_ALL_ITEMS as a Defined Flag but we relocate that part of the code to the plot script. The resulting script should look something like:

    #include "wrappers_h"
    #include "plot_h"
 
    #include "PLT_NO_GOOD_DEED"
 
    int StartingConditional()
    {
        // default result is false
        int nResult;
 
        event evEvent = GetCurrentEvent();
        int nEventType = GetEventType(evEvent);
 
        // the plot flag is the second value in the event's ints list
        int nPlotFlag = GetEventInteger(evEvent, 1);
 
        if(nEventType == EVENT_TYPE_GET_PLOT)
        {   
            switch(nPlotFlag)
            {
                case PLAYER_HAS_ALL_ITEMS:
                {
                    // check if all main flags have been raised
                    if(WR_GetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_FOOD)
                    && WR_GetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_DRINK)
                    && WR_GetPlotFlag("PLT_NO_GOOD_DEED", PLAYER_HAS_FLOWER))
                    {
                        nResult = TRUE;
                    }               
                    break;
                }
            }
        }
        return nResult;
    }

Option B

In this option we do away with the module script altogether. Instead of using Main Flags to determine if the player acquired the quest item we can use the dynamic nature of the Defined Flag and use the plot script to evaluate the current value of the PLAYER_HAS_ALL_ITEMS flag.

    #include "wrappers_h"
    #include "plot_h"
 
    #include "PLT_NO_GOOD_DEED"
 
    int StartingConditional()
    {
        // default result is false
        int nResult;
 
        event evEvent = GetCurrentEvent();
        int nEventType = GetEventType(evEvent);
 
        // the plot flag is the second value in the event's ints list
        int nPlotFlag = GetEventInteger(evEvent, 1);
 
        if(nEventType == EVENT_TYPE_GET_PLOT)
        {   
            switch(nPlotFlag)
            {
                case PLAYER_HAS_ALL_ITEMS:
                {       
                    // get a reference to the player
                    object oHero = GetHero();
 
                    // check if all items have been collected
                    if(IsObjectValid(GetItemPossessedBy(oHero, "food"))
                    && IsObjectValid(GetItemPossessedBy(oHero, "drink"))
                    && IsObjectValid(GetItemPossessedBy(oHero, "flower")))
                    {
                        nResult = TRUE;
                    }               
                    break;
                }
            }
        }
        return nResult;
    }
  • Incidentally just to prove BioWare developers do have a sense of humour one of the entries in rewards.2da is "HorseDrownsWhileFlying DO NOT USE". Okay may be that says more about my sense of humour than theirs.

See Also

I've also produced a number of demo modules which illustrate different aspects of the plot system: