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 -
-
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).
-
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.
-
Are multiple conditions meant to be ANDs or ORs?
-
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). -
-
Comments -
- 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.
- 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.
- Are multiple conditions meant to be ANDs or ORs?
ANDs.
- 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 sClassNameSomeone 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.