Scripting Request



  • This is something I want to do, but I'm so busy I can't even find some time to work on it. I don't expect players to do this entirely alone, but if someone wants to do something that will add a lot of value to many projects I have in my mind, this would help immensely.

    The task is to code a generic and flexible CoA Trigger for various purposes. I want to be able to only set a number of variables on a trigger in order for it to work out of the box. Those variables are outlined below ((There could be more variables, I might not have forseen them all)).

    Then the trigger would have the very same script in the OnEnter and OnExit event, and this particular script would evaluate said variables and take the appropriate action. It needs to be highly modular, so typically the OnEnter / OnExit script would be a very short main() function, with an include file for all the lenghty functions and verifications.

    There also need to be failsafes for certain variables that could be optional. If those variables are not set on the item, they should either be ignored, or replaced by default values.

    Variables

    sTriggerFiringEvent
    purpose: Determines on which event (Enter/Exit), the code will run.
    possible values: OnEnter / OnExit
    
    sTriggeringConditionType
    purpose: Determines which condition has to be met in order for the trigger to -actually- do something. (Multiple Values separated by commas can be used)
    possible values: PossessionOfItem / HasQuest / HasCompletedQuest / NumberOfPCEntered / NumberOfPCExited / PCClass / PCOverLevel / PCUnderLevel / PCIsFactionMember / PCBeatDC /
    
    sTriggeringConditionValue
    purpose: Contains the value for the Condition Type, for example, if the condition Type is HasQuest,PCClass then the value could be "qParner,Wizard"
    
    sResultScript
    purpose: Contains the script to execute if the Triggering Condition is met
    possible values: any script name
    
    sTagOfObjectToExecute
    purpose: the ResultScript could have to be fired on some object, PC, placeable, etc, if so, we need the tag of it.
    possible values: The tag of an object, or PC if it's on the PC. 
    
    iRandomnessOfTriggering
    purpose: Can randomize the triggering of the code by a certain percentage
    possible values: 1 to 100
    
    

    Functions Prototypes

    int isTriggeringConditionMet(string sTriggeringConditionType, string sTriggeringConditionValue);
    ---And load of others I didn't have the time to think about beforehand.
    
    


  • Comments -

    1. There is no mechanism by which a script can tell which event triggered it, if any. So, get rid of "sTriggerFiringEvent" and have seperate OnEnter and OnExit scripts, and put it in the "how to use this system" to only use one or the other and not both (though, that would be possible too I suppose if there were a reason for it).

    2. Scripts aren't fired on objects, they are fired BY objects. It would make the most sense to have the fired off scripts do their own grabbing of variables from the triggered object, getting passed only the triggering object (which is the only info they can't get on their own). They would have to be passed values by local variables on some object anyway.

    3. Are multiple conditions meant to be ANDs or ORs?

    4. Will need additional delimiters in the "ConditionalValue" in order to pass on more than one string to the conditional checking code ie. pass "Reflex:15" or "Spellcraft:20" to the "PCBeatDC" checking code. I'd suggest ":".

    Therefore I'd suggest change to the following specifications-

    sOnEnterConditionType 
    sOnExitConditionType
    purpose: Determines which condition has to be met in order for the trigger to -actually- do something. (Multiple Values separated by commas can be used, in which case all conditions must be met) 
    possible values: PossessionOfItem / HasQuest / HasCompletedQuest / NumberOfPCEntered / NumberOfPCExited / PCClass / PCOverLevel / PCUnderLevel / PCIsFactionMember / PCBeatDC / ect.
    
    sOnEnterConditionValue 
    sOnExitConditionValue
    purpose: Contains the value for the Condition Type, for example, if the condition Type is HasQuest,PCClass then the value could be "qParner,Wizard" or is PosessionofItem,PCBeatDC could be "WhiteStagMeat,Spellcraft:20"
    
    sOnEnterScript
    sOnExitScript
    purpose: Contains the name of the script to execute if the Triggering Condition is met.  OBJECT_SELF will be the triggered object, GetLocalObject(OBJECT_SELF,"oTriggeringPC") will be the triggering PC.
    
    sOnEnterScriptData
    sOnExitScriptData
    purpose : Contains and data which needs to be passed on to the triggered script.  Data will be in GetLocalObject(OBJECT_SELF,"sScriptData").  NOTE: Use "sScriptData" directly unless you are using two different OnEnter and OnExit conditional scripts.
    
    iOnEnterRandomness 
    iOnExitRandomness
    purpose: Can randomize the triggering of the code by a certain percentage 
    possible values: 1 to 100
    

    There will be three scripts - two very small OnEnter and OnExit and the larger #include to contain the function

    I'd also suggest that the main function be broken up into smaller functions (ie. each conditional gets it's own function, same name as the conditional, to which is passed the value for it to test).



  • @COA_Aristos:

    Comments -

    1. There is no mechanism by which a script can tell which event triggered it, if any. So, get rid of "sTriggerFiringEvent" and have seperate OnEnter and OnExit scripts, and put it in the "how to use this system" to only use one or the other and not both (though, that would be possible too I suppose if there were a reason for it).

    What I meant is that both OnEnter and OnExit will be the same script. Each of them will start by checking the variable on themselves. If they are the script supposed to fire, they continue, otherwise, they do nothing.

    Example: A PC walk over the trigger. It triggers OnEnter. The trigger run the script, checks the variable on itself, but the variable is set to OnExit, so it exits the function before doing anything. When the PC exit the trigger, then the function will run completely.

    1. Scripts aren't fired on objects, they are fired BY objects. It would make the most sense to have the fired off scripts do their own grabbing of variables from the triggered object, getting passed only the triggering object (which is the only info they can't get on their own). They would have to be passed values by local variables on some object anyway.

    I know, I meant BY.

    1. Are multiple conditions meant to be ANDs or ORs?

    ANDs.

    1. Will need additional delimiters in the "ConditionalValue" in order to pass on more than one string to the conditional checking code ie. pass "Reflex:15" or "Spellcraft:20" to the "PCBeatDC" checking code. I'd suggest ":".

    Yes, that could work.



  • What I meant is that both OnEnter and OnExit will be the same script. Each of them will start by checking the variable on themselves. If they are the script supposed to fire, they continue, otherwise, they do nothing.

    Example: A PC walk over the trigger. It triggers OnEnter. The trigger run the script, checks the variable on itself, but the variable is set to OnExit, so it exits the function before doing anything. When the PC exit the trigger, then the function will run completely.

    So, something like

    if GetLocalVariable(OBJECT_SELF,"sTriggerFiringEvent") != WhatEventTriggeredMe() return;
    

    Not possible.

    I suppose as a cludge you could determine if the PC is in the area of the trigger or area, if so it would be an OnEnter event if not an OnExit event. Maybe. Very prone to lag induced errors I would think. But doable.



  • I suppose that having the OnEnter and OnExit scripts be different could work, I don't mind much as long as both retain the same logic and can use the same parameters.

    OnEnter

    If sFiringEvent on OBJECT_SELF = OnEnter Then continue
    Else return;
    
    

    OnExit

    If sFiringEvent on OBJECT_SELF = OnExit Then continue
    Else return;
    
    


  • OnEnter

    // ConditionalTrigger_OnEnd (or whatever)
    
    #include "ConditionalTrigger_Include" // (or whatever)
    
    main()
    {
    // Find the PC who entered or exited - only trigger for PCs
         object oPC = GetEnteringObject();
    
    // Error checking code
         if !GetIsPC(oPC) || GetIsDM(oPC)
              return;
         if GetLocalString(OBJECT_SELF,"sTriggerFiringEvent") != "OnEnter"
              return;
         ActionConditonalTriggering(oPC);
    }
    

    OnExit

    // ConditionalTrigger_OnEnd (or whatever)
    
    #include "ConditionalTrigger_Include" // (or whatever)
    
    main()
    {
    // Find the PC who entered or exited - only trigger for PCs
         object oPC = GetExitingObject();
    
    // Error checking code
         if !GetIsPC(oPC) || GetIsDM(oPC)
              return;
         if GetLocalString(OBJECT_SELF,"sTriggerFiringEvent") != "OnExit"
              return;
         ActionConditionalTriggering(oPC);
    }
    

    Include file

    // "ConditionalTrigger_Include" (or whatever)
    #include "NW_I0_PLOT"
    
    // This function likely also allready exists in some include file in some COA system somewhere
    
    int GetClassNumberByName(string sClass)
    {
         if sClass == "Barbarian" return CLASS_TYPE_BARBARIAN;
         if sClass == "Cleric" return CLASS_TYPE_CLERIC;
         if sClass == "Druid" return CLASS_TYPE_DRUID;
         if sClass == "Fighter" return CLASS_TYPE_FIGHTER;
         if sClass == "Monk" return CLASS_TYPE_MONK;
         if sClass == "Paladin" return CLASS_TYPE_PALADIN;
         if sClass == "Ranger" return CLASS_TYPE_RANGER;
         if sClass == "Rogue" return CLASS_TYPE_ROGUE;
         if sClass == "Sorcerer" return CLASS_TYPE_SORCERER;
         if sClass == "Wizard" return CLASS_TYPE_WIZARD;
         if sClass == "Arcane Archer" return CLASS_TYPE_ARCANE_ARCHER;
         if sClass == "Assassin" return CLASS_TYPE_ASSASSIN;
         if sClass == "Devine Champion" return CLASS_TYPE_DEVINE_CHAMPION;
         if sClass == "Red Dragon Disciple" return CLASS_TYPE_DRAGON_DISCIPLE;
         if sClass == "RDD" return CLASS_TYPE_DRAGON_DISCIPLE;
         if sClass == "Harper Scout" return CLASS_TYPE_HARPER;
         if sClass == "Harper" return CLASS_TYPE_HARPER;
         if sClass == "Pale Master" return CLASS_TYPE_PALE_MASTER;
         if sClass == "Shadow Dancer" return CLASS_TYPE_SHADOWDANCER;
         if sClass == "Shifter" return CLASS_TYPE_SHIFTER;
         if sClass == "Weapon Master" return CLASS_TYPE_WEAPON_MASTER;
         return -1; // Invalid Class Name    
    }
    
    int isTriggeringConditionMet(object oPC,string sConditionType,string sConditionValue)
    {
    // Various setup things
         string sType;
         string sValue;
         string sValue2;
         int iDelimiterLocation;
    
         if (sConditionType == "") return FALSE; // No Condition then it can't be met
    
         while sConditionType != ""
         {
    // extract entries from the strings using delimiters "," and ":"
              iDelimiterLocation = FindSubString(sConditionType,",");
              if iDelimiterLocation == -1
                   sType = sConditionType;
              else {
                   sType = GetStringLeft(sConditionType,iDelimiterLocation);
                   sConditionType = GetStringRight(sConditionType,GetStringLength(sConditionType)-iDelimiterLocation-1); }
              iDelimiterLocation = FindSubString(sConditionValue,",");
              if iDelimiterLocation == -1
                   sValue = sConditionValue;
              else {          
                   sValue = GetStringLeft(sConditionValue,iDelimiterLocation);
                   sConditionValue = GetStringRight(sConditionValue,GetStringLength(sConditionValue)-iDelimiterLocation-1); }
              iDelimiterLocation = FindSubString(sValue,":");
              if iDelimiterLocation == -1
                   sValue2="";
              else {
                   sValue = GetStringLeft(sValue,iDelimiterLocation);
                   sValue2 = GetStringRight(sValue,GetStringLength(sValue)-iDelimiterLocation-1);
              }
    ///////////////////////////////////////////////////////////////
    //////////  HERE IS WHERE THE CONDITIONS ARE CHECKED //////////
    ///////////////////////////////////////////////////////////////
              if sType == "HasItem"
              {
                   if !HasItem(oPC,sValue) return FALSE;
              }
              else if sType == "UnderLevel"
              {
                   if !(GetHitDice(oPC) < StringToInt(sValue)) return FALSE;
              }
              else if sType == "OverLevel"
              {
                   if !(GetHitDice(oPC) > StringToInt(sValue)) return FALSE;
              }
              else if sType == "HasClass"
              {
                   if !(GetLevelByClass(oPC,GetClassNumberByName(sValue)) > 0) return FALSE;
              }
              else if sType == "UnderClassLevel"
              {
                   if !(GetLevelByClass(oPC,GetClassNumberByName(sValue)) < StringToInt(sValue2)) return FALSE;
              }
              else if sType == "OverClassLevel"
              {
                   if !(GetLevelByClass(oPC,GetClassNumberByName(sValue)) > StringToInt(sValue2)) return FALSE;
              }
    
              // ect. ect. ect. more else ifs need to be added for more conditionals //
              // Be sure to check if the condition is NOT met, and to return FALSE if NOT met //
    
              else return FALSE; // Unsupported conditions can not be met.
    
    ///////////////////////////////////////////////////////////////
    //////////  END WHERE CONDITIONS ARE CHECKED         //////////
    ///////////////////////////////////////////////////////////////
         }
         return TRUE;
    }
    
    void ActionConditionalTriggering(object oPC)
    {
    // Randomness - do first for efficiency
         int iRandomness = GetLocalInt(OBJECT_SELF,"iRandomnessOfTriggering");
         if (iRandomness != 0) && (iRandomness >= d100()) return;
    
    // Find the object to execute the script - exit if unable to find
         string sTagOfObjectToExecute = GetLocalString(OBJECT_SELF,"sTagOfObjectToExecute");
         object oExecutingObject = (sTagOfObjectToExecute == "") ? OBJECT_SELF :
                                   (sTagOfObjectToExecute == "SELF") ? OBJECT_SELF :
                                   (sTagOfObjectToExecute == "PC") ? oPC :
                                   GetObjectByTag(sTagOfObjectToExecute);
         if oExecutingObject == OBJECT_INVALID return;  // unable to find object, exit without comment
    
    // Find if condition is met
         if !isTriggeringConditionMet(oPC,
                                      GetLocalString(OBJECT_SELF,"sTriggeringConditionType"),
                                      GetLocalString(OBJECT_SELF,"sTriggeringConditionValue"))
              return; // exit without comment if condition is not met
    
    // Execute Script - invalid script name will perform no action.
         SetLocalObject(oExecutingObject,"oCT_PC",oPC); // not sure how to pass this info to the script
         SetLocalObject(oExecutingObject,"oCT_Trigger",OBJECT_SELF); // but probably have to do it somehow
         ExecuteScript(GetLocalString(OBJECT_SELF,"sResultScript"),oExecutingObject);
        return;
    }
    

    Implimented Conditions

    HasItem - sItemTag = checks if triggering PC has sItemTag in it's inventory
    UnderLevel - iLevel = checks if triggering PC has fewer hit dice than iLevel
    OverLevel - iLevel = checks if triggering PC has more hit dice than iLevel
    HasClass - sClassName = checks if triggering PC has at least 1 level in sClassName
    UnderClassLevel - sClassName:iLevel - checks if Triggering PC has fewer than iLevel levels in sClassName
    OverClassLevel - sClassName:iLevel - checks if Triggering PC has more then iLevel levels in sClassName

    Someone else will have to write the code for the other conditions, but it should be realitively easy



  • That's great! It'll save me some time!

    Awesome.

    Thank you.


Log in to reply