# Copyright (c) 2010, Trevor Bekolay
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#    * Redistributions of source code must retain the above copyright notice, this
#      list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright notice, this
#      list of conditions and the following disclaimer in the documentation and/or other
#      materials provided with the distribution.
#    * Neither the name of the IRCL nor the names of its contributors may be used to
#      endorse or promote products derived from this software without specific prior
#      written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

valid_nwvar_types = ("object", "int", "float", "string")
"""A tuple containing the valid variable types in NWScript.
@type: tuple of strings
"""

class ActualVerb(object):
    """ActualVerb holds information about an 'actual verb'.
    
    Actual verbs are the atomic actions defined in util_verbs.nss."""
    def __init__(self, name="", description=""):
        """Sets up the ActualVerb's instance variables.
        
        @keyword name: The name of the ActualVerb
        @type name: string
        @keyword description: The description of the ActualVerb
        @type description: string
        """
        self.name = name
        """ The name of the ActualVerb
        @type: string
        """
        self.description = description
        """ The description of the ActualVerb
        @type: string
        """
        self.vdarguments = []
        """ The VerbData arguments that this ActualVerb requires.
        @type: list of strings
        """
        self.varguments = []
        """ The arguments that this ActualVerb requires.
        Comprised of a list of tuples of the form::
            (Mandatory, Type, Name, Description)
        e.g.::
            (True, "float", "fSpeed", "The speed at which the animation plays, relative to the default 1.0")
        @type: list of tuples
        """

class Behaviour(object):
    """An object to hold all of the information necessary to generate the code\
    for a behaviour. Also contains methods to generate said code."""
    def __init__(self):
        """Set up the behaviour's instance variables."""
        self.verbs = []
        """The list of verbs making up this behaviour.
        @type: list of L{Verb}s
        """
        self.nwvariables = []
        """The list of variables and actors that are used in this behaviour.
        @type: list of L{NWVariable}s
        """
        self.name = "Behaviour"
        """The name of the behaviour.
        This is contrained to 12 characters, as the maximum length for an NWScript
        file is 16 characters and the behaviour name is prepended with "z_b_" for a script.
        @type: string
        """
    
    def __setattr__(self, name, value):
        """Intercept changes to the name and update each verb.
        @param name: Name of the attribute being set.
        @type name: string
        @param value: The value to set the attribute to.
        """
        if name == "name":
            if len(value) > 12:
                value = value[:11]
            for verb in self.verbs:
                verb.b_name = value
        
        object.__setattr__(self, name, value)
    
    def GenerateZBCode(self):
        """Generate the code for z_b_<behaviour> files.
        @return: The generated NWScript code.
        @rtype: string
        """
        # Get the variables that we'll need in our generated code.
        lower_name = self.name.lower()
        upper_name = self.name.capitalize()

        z_b_code = """\
/* CODE GENERATED BY BEHAVIOUR TOOL */

#include \"b_%(lower_name)s\"

// main will be called by a verb when it has been completed, through ExecuteScript.
void main()
{
    int iBehaviour = GetLocalInt(GetModule(), \"curr_behaviour\");
    int iReturn = getVerbReturn(iBehaviour);
    int iVerb = getVerbFinished(iBehaviour);

    if (iReturn == SUCCESS)
    {
        control_%(upper_name)s(iBehaviour, iVerb);
    }
    else
    {
        timeout_%(upper_name)s(iBehaviour);
    }
}"""    % {'lower_name':lower_name, 'upper_name':upper_name}
        
        return z_b_code

    def GenerateBCode(self):
        """Generate the code for b_<behaviour> files.
        @return: The generated NWScript code.
        @rtype: string
        """
        # Get the strings that we will need to plug into our shell
        
        # <behaviour name all lowercase>
        # e.g.:
        # fight
        lower_name = self.name.lower()

        # <behaviour name with the first letter capitalized>
        # e.g.:
        # Fight
        upper_name = self.name.capitalize()

        # VERBx: <context_name> <actual_name> <follower> <terminal>
        #     VERBx_PRECONDITIONS: <preconditions seperated by ;;>
        #     VERBx_FOLLOWERS: <followers seperated by spaces>
        #     VERBx_VERBDATA: <vdarguments seperated by spaces>
        #     VERBx_ARGUMENTS: <varguments seperated by ;;>
        # e.g.:
        # VERB1: instigate FaceAndSayLine Follower 
        #     VERB1_FOLLOWERS: retaliate callforhelp 
        #     VERB1_VERBDATA: oInstigator oVictim 
        #     VERB1_ARGUMENTS: "Hey punk!  It's fightin' time!"
        verb_info_list = []
        for ix, v in enumerate(self.verbs):
            if v.follower == True:
                follow_text = "Follower"
            else:
                follow_text = "Supporter"

            if v.terminal == True:
                terminal_text = "Terminal"
            else:
                terminal_text = ""

            verb_info_list.append("VERB%d: %s %s %s %s" % (ix+1, v.context_name, v.actual_name, follow_text, terminal_text))

            if len(v.preconditions) > 0:
                precond_text = "    VERB%d_PRECONDITIONS: " % (ix+1)
                for precond in v.preconditions:
                    precond_text += (precond + ";; ")
                verb_info_list.append(precond_text[:-3])

            if v.terminal == False:
                followers_text = "    VERB%d_FOLLOWERS: " % (ix+1)
                for follow in v.followers:
                    followers_text += (follow.context_name + ' ')
                verb_info_list.append(followers_text)

            vdargs_text = "    VERB%d_VERBDATA: " % (ix+1)
            for vdarg in v.vdarguments:
                vdargs_text += (vdarg + ' ')
            verb_info_list.append(vdargs_text)

            if len(v.varguments) > 0:
                vargs_text = "    VERB%d_ARGUMENTS: " % (ix+1)
                for varg in v.varguments:
                    vargs_text += (varg + ";; ")
                verb_info_list.append(vargs_text[:-3])
        verb_info = '\n'.join(verb_info_list)

        # ACTORx: <type> <name> <description>
        # e.g.:
        # ACTOR1: object oInstigator The instigator starts the fight.
        actor_info_list = []
        ix = 1
        for actor in self.nwvariables:
            if actor.isActor == True:
                actor_info_list.append("ACTOR%d: %s %s %s" % (ix, actor.type, actor.name, actor.description))
                ix += 1
        actor_info = '\n'.join(actor_info_list)

        # VARIABLEx: <type> <name> <description>
        # e.g.:
        # VARIABLE1: string sConv The conversation file to be run
        nwvar_info_list = []
        ix = 1
        for nwvar in self.nwvariables:
            if nwvar.isActor == False:
                nwvar_info_list.append("VARIABLE%d: %s %s %s" % (ix, nwvar.type, nwvar.name, nwvar.description))
                ix += 1
        nwvar_info = '\n'.join(nwvar_info_list)

        # const int <constant_name> = <id>
        # e.g.:
        # const int F_INSTIGATE_28 = 2
        constants_list = []
        for index, v in enumerate(self.verbs):
            if (v.follower == True):
                id = (index*2)+2 # + 2 to prevent an ID of 0
            else:
                id = (index*2)+1 # + 1 to make supporters odd
            constants_list.append("const int %s = %d" % (v.constant_name, id))
        constants = '\n'.join(constants_list)

        # //  <name> - <description>
        # e.g.:
        # //  oInstigator - The instigator starts the fight.
        nwvar_comments_list = []
        for nwvar in self.nwvariables:
            nwvar_comments_list.append("//  %s - %s" % (nwvar.name, nwvar.description))
        nwvar_comments = '\n'.join(nwvar_comments_list)

        # , <type> <name>
        # e.g.:
        # , object oInstigator, object oVictim, string sConv
        nwvar_list = []
        for nwvar in self.nwvariables:
            nwvar_list.append(", %s %s" % (nwvar.type, nwvar.name))
        nwvars = ''.join(nwvar_list)
        
        # <name> != <invalid value>
        # e.g.:
        # oInstigator != OBJECT_INVALID && oVictim != OBJECT_INVALID && sConv != ""
        invalids_list = []
        for nwvar in self.nwvariables:
            invalids_list.append("%s != %s" % (nwvar.name, nwvar.GetInvalidValue()))
        invalids = " && ".join(invalids_list)

        # b_Checkout(<name>, iID, iInterrupt)
        # e.g.:
        # b_Checkout(oInstigator, iID, iInterrupt) && b_Checkout(oVictim, iID, iInterrupt)
        checkout_list = []
        for nwvar in self.nwvariables:
            if nwvar.isActor == True:
                checkout_list.append("b_Checkout(%s, iID, iInterrupt)" % (nwvar.name))
        checkouts = " && ".join(checkout_list)

        # <first verb constant_name>
        # e.g.:
        # F_INSTIGATE_28
        if len(self.verbs) > 0:
            firstverb_name = self.verbs[0].constant_name
        else:
            firstverb_name = ""

        # SetLocal<type>(o<behaviour name capitalized>, "<name>", <name>);
        # e.g.:
        # SetLocalObject(oFight, "oInstigator", oInstigator);
        # SetLocalObject(oFight, "oVictim", oVictim);
        # SetLocalString(oFight, "sConv", sConv);
        setlocal_list = []
        for nwvar in self.nwvariables:
            setlocal_list.append("            SetLocal%s(o%s, \"%s\", %s);" % (nwvar.type.capitalize(), upper_name, nwvar.name, nwvar.name))
        setlocals = '\n'.join(setlocal_list)

        # <type> <name> = GetLocal<type>(o<behaviour name capitalized>, "<name>");
        # e.g.:
        # // Get the actors and other variables from the behaviour object.
        # object oInstigator = GetLocalObject(oFight, "oInstigator");
        # object oVictim = GetLocalObject(oFight, "oVictim");
        # string sConv = GetLocalString(oFight, "sConv");
        getlocals_list = ["    // Get the actors and other variables from the behaviour object."]
        for nwvar in self.nwvariables:
            getlocals_list.append("    %s %s = GetLocal%s(o%s, \"%s\");" % (nwvar.type, nwvar.name, nwvar.type.capitalize(), upper_name, nwvar.name))
        getlocals = '\n'.join(getlocals_list)

        # case <constant_name>:
        #         <verb's generated checkcues code>
        #         break;
        # e.g.:
        # case F_INSTIGATE_28:
        #     ...
        #     break;
        # case F_RETALIATE_G8:
        #     ...
        #     break;
        # case S_CALLFORHELP_29:
        #     ...
        #     break;
        checkcues_switch_list = []
        for v in self.verbs:
            checkcues_switch_list.append("        case %s:\n%s\n            break;" % (v.constant_name, v.GenerateCheckcuesCode()))
        checkcues_switch = '\n'.join(checkcues_switch_list)

        # case <constant_name>:
        #         <verb's generated control code>
        #         break;
        # e.g.:
        # case F_INSTIGATE_28:
        #     ...
        #     break;
        # case F_RETALIATE_G8:
        #     ...
        #     break;
        # case S_CALLFORHELP_29:
        #     ...
        #     break;
        control_switch_list = []
        for v in self.verbs:
            control_switch_list.append("    case %s:\n%s\n        break;" % (v.constant_name, v.GenerateControlCode()))
        control_switch = '\n'.join(control_switch_list)

        # iPrevious = b_GetPausedBehaviour(<name>);
        # if (iPrevious)
        # {
        #     setSegueToBehaviour(iPrevious, TRUE);
        # }
        # e.g.:
        # iPrevious = b_GetPausedBehaviour(oInstigator);
        # if (iPrevious)
        # {
        #     setSegueToBehaviour(iPrevious, TRUE);
        # }
        # 
        # iPrevious = b_GetPausedBehaviour(oVictim);
        # if (iPrevious)
        # {
        #     setSegueToBehaviour(iPrevious, TRUE);
        # }
        segue_previous_list = []
        for nwvar in self.nwvariables:
            if nwvar.isActor == True:
                segue_previous_list.append("""\
        iPrevious = b_GetPausedBehaviour(%s);
        if (iPrevious)
        {
            setSegueToBehaviour(iPrevious, TRUE);
        }""" % (nwvar.name))
        segue_previous = "\n\n".join(segue_previous_list)

        # b_Checkin(<name>);
        # e.g.:
        # b_Checkin(oInstigator);
        # b_Checkin(oVictim);
        checkin_list = []
        for nwvar in self.nwvariables:
            if nwvar.isActor == True:
                checkin_list.append("    b_Checkin(%s);" % (nwvar.name))
        checkin = '\n'.join(checkin_list)

        # Generate the code
        
        b_code = """\
/* CODE GENERATED BY BEHAVIOUR TOOL
Don't delete this comment block! You can, however, modify the elements.  Changes will show up when you reload the file.
BEHAVIOUR: %(upper_name)s
%(verb_info)s
%(actor_info)s
%(nwvar_info)s
 */
        
#include \"util_bintarray\"
#include \"util_behaviour\"
#include \"util_verbs\"

/////////// Constants ///////////
%(constants)s

/////////// Declarations ///////////

// Initializes the %(upper_name)s behaviour.
%(nwvar_comments)s
int start_%(upper_name)s(int iTimeout, int iInterrupt%(nwvars)s);
void checkcues_%(upper_name)s(int iTimeout, int iBehaviour);
void control_%(upper_name)s(int iBehaviour, int iVerb);
void cleanup_%(upper_name)s(int iBehaviour);
void timeout_%(upper_name)s(int iBehaviour);
void segue_%(upper_name)s(int iBehaviour);

/////////// Implementations ///////////

int start_%(upper_name)s(int iTimeout, int iInterrupt%(nwvars)s)
{
    // Make sure we have have what we need to start the behaviour.
    if (%(invalids)s)
    {
        object o%(upper_name)s = createBehaviourObject();
        int iID = GetLocalInt(o%(upper_name)s, \"ID\");

        // Check out our actors.
        if (%(checkouts)s)
        {
            // Set necessary parameters on the behaviour object.
%(setlocals)s
            SetLocalInt   (o%(upper_name)s, \"iInterrupt\", iInterrupt);
            SetLocalString(o%(upper_name)s, \"sScript\", \"z_b_%(lower_name)s\");

            // Set up the cue array and add the first step to it.
            intarrayNew(o%(upper_name)s, \"cues\");
            addCue(iID, %(firstverb_name)s);

            checkcues_%(upper_name)s(iTimeout, iID);

            return iID;
        }
    }

    return FALSE;
}

void checkcues_%(upper_name)s(int iTimeout, int iBehaviour)
{
%(getlocals)s

    // Are we finished?
    if (getBehaviourFinished(iBehaviour))
    {
        cleanup_%(upper_name)s(iBehaviour);
        return;
    }

    // Check to see if we want to segue before we check if we're paused.
    if (getSegueToBehaviour(iBehaviour))
    {
        segue_%(upper_name)s(iBehaviour);
        setSegueToBehaviour(iBehaviour, FALSE);
        setBehaviourPaused(iBehaviour, FALSE);
        DelayCommand(2*DEFAULT_HEARTBEAT_DELAY, checkcues_%(upper_name)s(iTimeout, iBehaviour));
        return;
    }

    // If we're paused, we'll just idle (no need to check as often as usual).
    if (getBehaviourPaused(iBehaviour))
    {
        DebugPrint(\"%(upper_name)s paused...\");
        DelayCommand(4*DEFAULT_HEARTBEAT_DELAY, checkcues_%(upper_name)s(iTimeout, iBehaviour));
        return;
    }

    // Have we timed out?
    if (!iTimeout)
    {
        timeout_%(upper_name)s(iBehaviour);
        DelayCommand(DEFAULT_HEARTBEAT_DELAY, checkcues_%(upper_name)s(iTimeout, iBehaviour));
        return;
    }

    // Run through each cue and see if any of them fire
    int c_ix, c_max, iVerb;

    c_max = getNumCues(iBehaviour);

    for (c_ix = 0; c_ix < c_max; c_ix++)
    {
        iVerb = getNextCue(iBehaviour);
        DebugPrint(\"Checking verb \" + IntToString(iVerb));

        switch (iVerb)
        {
%(checkcues_switch)s
        }
    }

    DelayCommand(DEFAULT_HEARTBEAT_DELAY, checkcues_%(upper_name)s(iTimeout-1, iBehaviour));

    DebugPrint(\"%(upper_name)s \" + IntToString(iBehaviour) + \" tick...\");
}

void control_%(upper_name)s(int iBehaviour, int iVerb)
{
%(getlocals)s

    switch (iVerb)
    {
%(control_switch)s
    }
}

void cleanup_%(upper_name)s(int iBehaviour)
{
%(getlocals)s
    int iInterrupt = GetLocalInt(o%(upper_name)s, \"iInterrupt\");

    // If we paused behaviours, we'll segue back into them.
    if (iInterrupt == INTERRUPT_PAUSE)
    {
        int iPrevious;
        
%(segue_previous)s
    }

%(checkin)s
    destroyBehaviourObject(iBehaviour);
}

void timeout_%(upper_name)s(int iBehaviour)
{
%(getlocals)s

    // Insert timeout handling code here

    setBehaviourFinished(iBehaviour);
}

void segue_%(upper_name)s(int iBehaviour)
{
%(getlocals)s

    // Insert segue handling code here
}
""" %   {"lower_name":lower_name,
         "upper_name":upper_name,
         "verb_info":verb_info,
         "actor_info":actor_info,
         "nwvar_info":nwvar_info,
         "constants":constants,
         "nwvar_comments":nwvar_comments,
         "nwvars":nwvars,
         "invalids":invalids,
         "checkouts":checkouts,
         "setlocals":setlocals,
         "getlocals": getlocals,
         "firstverb_name":firstverb_name,
         "checkcues_switch":checkcues_switch,
         "control_switch":control_switch,
         "segue_previous":segue_previous,
         "checkin":checkin}
    
        return b_code

class NWVariable(object):
    """Contains information about an NWScript variable.
    
    The NWVariable class represents any variable that you can pass to a behaviour.
    This includes lines that can change based on where the behaviour is called,
    strings representing conversation files, flags, objects, etc.
    
    This class also takes care of actors; actors are just a special case of a
    normal variable, except that each behaviour requires at least one actor,
    and actors are always objects."""
    def __init__(self, type="", name="", description="", isActor = False):
        """Sets up all the instance variables."""
        self.type = type
        """The type of the NWVariable.
        Valid types are defined in L{valid_nwvar_types}.
        @type: string
        """
        self.name = name
        """The name of the NWVariable.
        NWVariable names should follow conventions; the first letter of the variable
        is the first letter of the variable type, and the second letter is capitalized.
        
        e.g.: C{oActorA, sConversation}
        @type: string
        """
        self.description = description
        """A short description of the NWVariable.
        These descriptions are only used in comments in the generated script,
        but they are nevertheless indispensable.
        @type: string
        """
        self.isActor = isActor
        """Whether or not this NWVariable is an actor.
        @type: bool
        """

    def GetInvalidValue(self):
        """Each NWScript type has an 'invalid' (default) value.
        We need these to test if we have been passed valid NWVariables.
        @return: The proper invalid value for the NWVariable's type.
        @rtype: string"""
        if self.type == "object":
            return "OBJECT_INVALID"
        elif self.type == "int":
            return "-1"
        elif self.type == "float":
            return "0.0"
        elif self.type == "string":
            return "\"\""
        else:
            return None

class Verb(object):
    """Verb generates code for each verb object.

    To use, change the attributes, then simply access the control attribute
    to see the generated code for the control_<Behaviour> section.  Access
    the checkcues attribute to see the generated code for the
    checkcues_<Behaviour> section."""
    def __init__(self, behaviour):
        """Sets up all the instance variables."""
        self.context_name = ""
        """A name that can identify the verb in the context of the behaviour.
        @type: string
        """
        self.b_name = behaviour.name.upper()
        """The name of the behaviour this verb is a part of.
        This is required when generating the constant_name.
        @type: string
        """
        self.constant_name = ""
        """The constant that will be used to identify the verb in switch statements.
        
        It will be appended with the first two letters of the behaviour name to deal
        with conflicts (if one includes two behaviours with the same constant name,
        the script won't compile).
        
        e.g.: C{F_FI_INSTIGATE}
        @type: string
        """
        self.actual_name = ""
        """The name of the actual verb.
        
        e.g.: C{FaceAndSayLine}
        @type: string
        """
        self.follower = True
        """Is this verb a follower or a supporter?
        @type: bool
        """
        self.terminal = False
        """Does the behaviour end at the termination of this verb?
        @type: bool
        """
        self.followers = []
        """The verbs that follow this verb.  Terminal verbs have no followers.
        @type: list of L{Verb}s
        """
        self.preconditions = []
        """The preconditions that must be met before this verb will execute.
        @type: list of strings
        """
        self.vdarguments = []
        """Arguments for the creation of the VerbData object (oSubject, oDirObject, etc.)
        @type: list of strings
        """
        self.varguments = []
        """Arguments to pass to the actual verb when it's called.
        Not all verbs require arguments to be passed, so this can be empty.
        @type: list strings
        """
    
    def __setattr__(self, name, value):
        """Intercept assignments to the context_name or follower attributes,
        so we can generate the constant_name automatically.
        @param name: Name of the attribute being set.
        @type name: string
        @param value: The value to set the attribute to.
        """
        if name == "b_name":
            if len(value) < 2:
                value = "BH"
            value = value.upper()
        
        object.__setattr__(self, name, value)
        
        if name == "context_name" or name == "follower" or name == "b_name":
            # Only try to set the const_name if we have all the required attributes.
            if hasattr(self, "follower") and hasattr(self, "context_name") and hasattr(self, "b_name"):
                if object.__getattribute__(self, "follower") == False:
                    startchar = 'S'
                else:
                    startchar = 'F'
                
                const_name = "%c_%c%c_%s" % (startchar, self.b_name[0], self.b_name[1], self.context_name.upper())
                object.__setattr__(self, "constant_name", const_name)           
    
    def GenerateCheckcuesCode(self):
        """Generate the cueckcues code.
        @return: The generated NWScript code.
        @rtype: string
        """
        # if(<preconditions joined by &&>)
        # e.g.:
        # if ((GetArea(GetNearestCreature(CREATURE_TYPE_ALIVE, TRUE, 2)) == GetArea(oVictim)) && (!GetIsDead(GetNearestCreature(CREATURE_TYPE_ALIVE, TRUE, 2))))
        if len(self.preconditions) == 0:
            preconditions = ""
        else:
            preconditions = "if (%s)\n            " % (" && ".join(self.preconditions))

        # <actual verb name>
        # e.g.:
        # FaceAndSayLine
        actual = self.actual_name

        # , <VerbData arguments>
        # e.g.:
        # , oInstigator, oVictim
        vdarg_list = []
        for vdarg in self.vdarguments:
            if vdarg != "":
                vdarg_list.append(", %s" % (vdarg))
        vdargs = ''.join(vdarg_list)

        # , <actual verb arguments>
        # e.g.:
        # , "Oh, you're picking on the wrong person, fool...", sConv
        varg_list = []
        for varg in self.varguments:
            if varg != "":
                varg_list.append(", %s" % (varg))
        vargs = ''.join(varg_list)

        # clearFollowerCues(iBehaviour); OR removeCue(iBehaviour, <constant_name>);
        # e.g.:
        # removeCue(iBehaviour, S_CALLFORHELP_29);
        if self.follower == True:
            clear = "clearFollowerCues(iBehaviour);"
        else:
            clear = "removeCue(iBehaviour, %s);" % (self.constant_name)

        # Generate the code

        checkcues = """\
            %(preconditions)s{
                %(actual)s (VerbData(iVerb, iBehaviour%(vdargs)s)%(vargs)s);
                %(clear)s
                c_max = getNumCues(iBehaviour);
            }""" % {"preconditions":preconditions,
                  "actual":actual,
                  "vdargs":vdargs,
                  "vargs":vargs,
                  "clear":clear}
        
        return checkcues

    def GenerateControlCode(self):
        """Generate the control code.
        @return: The generated NWScript code.
        @rtype: string
        """
        
        # setBehaviourFinished(iBehaviour);
        if self.terminal == True:
            # Generate the code

            control = "        setBehaviourFinished(iBehaviour);"

        else:
            # addCue(iBehaviour, <follower constant name>);
            # e.g.:
            # addCue(iBehaviour, F_RETALIATE_G8);
            # addCue(iBehaviour, S_CALLFORHELP_29);
            addcues_list = []
            for follow in self.followers:
                addcues_list.append("        addCue(iBehaviour, %s);" % (follow.constant_name))

            # Generate the code

            control = '\n'.join(addcues_list)
        
        return control
