Author Topic: What's the point?  (Read 1492 times)

Legacy_Highv Priest

  • Full Member
  • ***
  • Posts: 170
  • Karma: +0/-0
What's the point?
« on: February 07, 2013, 12:28:52 am »


               Ok as a standard scripter for a couple years, I have to ask... What's the difference between using ExecuteScript and using an in-script block of code?

For example...

ExecuteScript("blah", OBJECT_SELF);

Blah = void main(){ AssignCommand(OBJECT_SELF, SpeakString("Blah"));}

OR

void Blah(object oSelf)
{
AssignCommand(oSelf, SpeakString("Blah"));
}

The only difference I see is that executescript is an absolute waste... Instead of simply using 1 script to handle things, you're using at minimum.. 2(and using mathematical logic, I have to assume that the computer having to only use 1 script file is cheaper then the computer having to use 2). It'd be great if executescript would transfer ownership of the script to the person it's being executed on(especially since it gives them the title of OBJECT_SELF), but it doesn't.

Does ExecuteScript do anything useful?
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
What's the point?
« Reply #1 on: February 07, 2013, 01:10:13 am »


               

Highv Priest wrote...

It'd be great if executescript would transfer ownership of the script to the person it's being executed on(especially since it gives them the title of OBJECT_SELF), but it doesn't.

it does actually

Does ExecuteScript do anything useful?

of course, it allows to change things globally, a good example of this is x2_pc_umdcheck script

or the script 70_spellhook thats coming with my unofficial patch - it allows to change anything in internal spellhook without need to recompile all spellscripts
               
               

               
            

Legacy_Highv Priest

  • Full Member
  • ***
  • Posts: 170
  • Karma: +0/-0
What's the point?
« Reply #2 on: February 07, 2013, 01:16:51 am »


               It doesn't transfer ownership(at least for effects which are the only things that have "GetCreator" anyway). For instance if you use executescript on a PC from a module event. The effect applied is applied under the module and not the PC. The executescript as far as I can tell only sets the OBJECT_SELF to be the thing it's running on, but doesn't flag it as the owner of the script being run.
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
What's the point?
« Reply #3 on: February 07, 2013, 01:31:35 am »


               

Highv Priest wrote...

It doesn't transfer ownership(at least for effects which are the only things that have "GetCreator" anyway). For instance if you use executescript on a PC from a module event. The effect applied is applied under the module and not the PC. The executescript as far as I can tell only sets the OBJECT_SELF to be the thing it's running on, but doesn't flag it as the owner of the script being run.

hmm thats new for me, but not surprising actually. Unline signal event/assign command, delay command, the script being executed still counts towards current code block and instruction limit. To change it, a delay must be added. Then the ownership (now I know what you've meant - i thought you mean object_self) changes. Unfortunatelly with delay you lose other informations... but anyway my previous response still apply. Just instead ownership I meant caller.

An another advantage of executecommand is when you need to change caller of the function and return value. You can't do that with assigncommand, at least not in original code block as AssignCommand is executed after the code block is ended.
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
What's the point?
« Reply #4 on: February 07, 2013, 01:51:44 am »


               

Highv Priest wrote...

Ok as a standard scripter for a couple years, I have to ask... What's the difference between using ExecuteScript and using an in-script block of code?

For example...

ExecuteScript("blah", OBJECT_SELF);

Blah = void main(){ AssignCommand(OBJECT_SELF, SpeakString("Blah"));}

OR

void Blah(object oSelf)
{
AssignCommand(oSelf, SpeakString("Blah"));
}

The only difference I see is that executescript is an absolute waste... Instead of simply using 1 script to handle things, you're using at minimum.. 2(and using mathematical logic, I have to assume that the computer having to only use 1 script file is cheaper then the computer having to use 2). It'd be great if executescript would transfer ownership of the script to the person it's being executed on(especially since it gives them the title of OBJECT_SELF), but it doesn't.

Does ExecuteScript do anything useful?


All kinds of things. To transfer ownership, just AssignCommand the execution.

That aside, however, it's incredibly useful for flexibiity and flow control, and can save you a lot of duplicative scripting. In our generic modwide area entry script, for example, if we want to do area-specific things, we just ES them based on a variable set on the area:

    /* execute specific onenter script if specified */
    string sScript = GetLocalString(oArea, "Area_OnEnter");
    if (sScript != "")
        ExecuteScript(sScript, oArea);

Likewise, our dynamic convo scriptset would not be possible without it:


#include "hg_inc"
#include "ac_dynconv_inc"

int StartingConditional () {
    object oPC = GetPCSpeaker();

    int i, nMax = GetLocalInt(oPC, "DynConv_Filter_Max");
    for (i = 0; i < nMax; i++)
        DeleteLocalInt(oPC, "DynConv_Filter_" + IntToString(i));
    DeleteLocalInt(oPC, "DynConv_Filter_Max");

    string sScript = GetLocalString(oPC, "DynConv_Script");
    if (sScript == "")
        return FALSE;

    if (GetLocalInt(oPC, "DynConv_End")) {
        DeleteLocalInt(oPC, "DynConv_End");
        return FALSE;
    }


    /* execute script callbacks as needed */
    if (GetLocalInt(oPC, "DynConv_Stage") == DYNCONV_STAGE_INIT)
        ExecuteScript(sScript, oPC);

    SetLocalInt(oPC, "DynConv_Stage", DYNCONV_STAGE_MENU);
    ExecuteScript(sScript, oPC);

Ditto our encounter system:

void EncExecuteScripts (object oTrig, object oArea, string sType) {
    struct SubString ss;
    ss.rest = GetLocalString(oArea, "Enc" + sType + "Scripts");

    string sScripts = GetLocalString(oTrig, "Enc" + sType + "Scripts");

    if (ss.rest != "" && sScripts != "")
        ss.rest += " ";

    ss.rest += sScripts;

    while (ss.rest != "") {
        ss = GetFirstSubString(ss.rest);

        ExecuteScript(ss.first, oTrig);
    }
}

Our trap system:

void TrapExecuteScripts (object oTrig, object oArea, string sType) {
    struct SubString ss;
    ss.rest = GetLocalString(oArea, "Trap" + sType + "Scripts");

    string sScripts = GetLocalString(oTrig, "Trap" + sType + "Scripts");

    if (ss.rest != "" && sScripts != "")
        ss.rest += " ";

    ss.rest += sScripts;

    while (ss.rest != "") {
        ss = GetFirstSubString(ss.rest);

        ExecuteScript(ss.first, oTrig);
    }
}

It's also very useful for avoiding tangling includes, or for executing blocks of code you call a lot from different places - that is, it assists in modularity. We use it to call up our 'resurrection' event, for example, from all sorts of different scripts:

afx_deathcrown (23):         AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
afx_deathcrown (117):             AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
afx_deathcrown (206):             AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
afx_deathcrown (295):         AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
afx_deathcrown (323):                     AssignCommand(oJump, ExecuteScript("fky_deathprocess", oJump));
afx_deathcrown (354):             AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
afx_deathcrown (374):             AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
afx_deathcrown (395):             AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
afx_deathcrown (420):             AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
afx_deathcrown (436):             AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));
ca_clr_massrez (32):             AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
es_miracle (28):         AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
ev_lifetransrod (34):     AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
fky_chat_dm_comm (1123):                                 AssignCommand(oDMTarget, ExecuteScript("fky_deathprocess", oDMTarget));//this includes dar, which is problematic because of crossincluding with hgll and chat
guildcontestport (48):                     AssignCommand(oGuilder, ExecuteScript("fky_deathprocess", oGuilder));
hell_amodeus_dth (32):             AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
hg_inc (3899):             AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
hg_inc (3936):             AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
hg_respawn (245):     AssignCommand(oArea, ExecuteScript("fky_deathprocess", oPC));
nw_s0_raisdead (119):     AssignCommand(si.target, ExecuteScript("fky_deathprocess", si.target));
nw_s2_bardsong (204):             AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
paragon_spell (1424):                 AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
randuse (65):     AssignCommand(oTarget, ExecuteScript("fky_deathprocess", oTarget));
voyageskip (159):             AssignCommand(oPC, ExecuteScript("fky_deathprocess", oPC));

Likewise, instead of rewriting a dozen different variations on the same code, you can generalize it, and make the similar code modular, to accept the variations. By way of example, we gave our Red Dragon Disciples a fear aura. Rather than rewriting all the special aura code we have to ensure that auras fire (they're normally very low priority), we simply route that ability through existing code:


ca_rdd_fearaura (27):         AssignCommand(oPC, ExecuteScript("nw_s1_aurafear", oPC));

Likewise, our gnomish inventors use bombs that emulate spell effects:

x2_s3_bomb (61):     if (GetTag(oItem) == "gi_it_bombs") {   /* Gnomish Inventor Bombs - these rely on GetSpellInfo not firing until they ExecuteScript */
x2_s3_bomb (68):             case GI_BOMB_CUSSER:    ExecuteScript("x2_s2_cursesong",    OBJECT_SELF);   return;
x2_s3_bomb (69):             case GI_BOMB_EASER:     ExecuteScript("nw_s2_bardsong",     OBJECT_SELF);   return;
x2_s3_bomb (70):             case GI_BOMB_BLOWER:    ExecuteScript("x0_s0_gustwind",     OBJECT_SELF);   return;
x2_s3_bomb (71):             case GI_BOMB_STICKER:   ExecuteScript("x2_s0_stnehold",     OBJECT_SELF);   return;
x2_s3_bomb (72):             case GI_BOMB_GREASER:   ExecuteScript("nw_s0_grease",       OBJECT_SELF);   return;
x2_s3_bomb (73):             case GI_BOMB_TEASER:    ExecuteScript("x0_s0_laugh",        OBJECT_SELF);   return;
x2_s3_bomb (74):             case GI_BOMB_MELTER:    ExecuteScript("nw_s0_acidfog",      OBJECT_SELF);   return;
x2_s3_bomb (75):             case GI_BOMB_RUMBLER:   ExecuteScript("hgs_gen_sphere",     OBJECT_SELF);   return;
x2_s3_bomb (76):             case GI_BOMB_NETTER:    ExecuteScript("nw_s0_entangle",     OBJECT_SELF);   return;
x2_s3_bomb (77):             case GI_BOMB_CRACKER:   ExecuteScript("x2_s0_grtthdclp",    OBJECT_SELF);   return;
x2_s3_bomb (78):             case GI_BOMB_SHOCKER:   ExecuteScript("hgs_gen_sphere",     OBJECT_SELF);   return;
x2_s3_bomb (80):             case GI_BOMB_CLAPPER:   ExecuteScript("x2_s0_grtthdclp",    OBJECT_SELF);   return;
x2_s3_bomb (81):             case GI_BOMB_SMOKER:    ExecuteScript("qc_gi_smoker_aoe",   OBJECT_SELF);   return;
x2_s3_bomb (82):             case GI_BOMB_WARPER:    ExecuteScript("nw_s0_timestop",     OBJECT_SELF);   return;
x2_s3_bomb (83):             case GI_BOMB_CRUNCHER:  ExecuteScript("nw_s0_implosion",    OBJECT_SELF);   return;
x2_s3_bomb (84):             case GI_BOMB_FUMER:     ExecuteScript("nw_s0_wailbansh",    OBJECT_SELF);   return;

I could go on, and on, and on. It is one of THE MOST useful functions out there.

Funky
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
What's the point?
« Reply #5 on: February 07, 2013, 01:53:28 am »


               

Highv Priest wrote...

It doesn't transfer ownership(at least for effects which are the only things that have "GetCreator" anyway). For instance if you use executescript on a PC from a module event. The effect applied is applied under the module and not the PC. The executescript as far as I can tell only sets the OBJECT_SELF to be the thing it's running on, but doesn't flag it as the owner of the script being run.


It does transfer ownership,   Even for effects.    If from a module event you execute a script on the PC and the script that is now running on the PC creates an effect, the PC will be the creator not the module.  
               
               

               
            

Legacy_Highv Priest

  • Full Member
  • ***
  • Posts: 170
  • Karma: +0/-0
What's the point?
« Reply #6 on: February 07, 2013, 02:16:23 am »


               I just find it annoying lol. Being the unfortunate scripter of a world with 5377 scripts, it gets damn tiring to find out sooooo many of them were created purely for the use of execute script >.<
The mod is already at 15992 resources and I'm having to delete/fix many things done by the previous owners(back when they asked me to continue work on it for their lack of desire to do it anymore) to be able to add new content. Thank you for the answer though. The global aspect of it definitely is a good purpose.
               
               

               
            

Legacy_Highv Priest

  • Full Member
  • ***
  • Posts: 170
  • Karma: +0/-0
What's the point?
« Reply #7 on: February 07, 2013, 02:33:57 am »


               

Lightfoot8 wrote...

Highv Priest wrote...

It doesn't transfer ownership(at least for effects which are the only things that have "GetCreator" anyway). For instance if you use executescript on a PC from a module event. The effect applied is applied under the module and not the PC. The executescript as far as I can tell only sets the OBJECT_SELF to be the thing it's running on, but doesn't flag it as the owner of the script being run.


It does transfer ownership,   Even for effects.    If from a module event you execute a script on the PC and the script that is now running on the PC creates an effect, the PC will be the creator not the module.  


You must forgive me if I sound arrogant, but I know for certain that executescript does not change "GetEffectCreator" which is how I determine "ownership" of the script. Example =

if(GetLevelByclass(class_TYPE_ASSASSIN, oPC) >= 3)
        {
        ExecuteScript("bloodoath", oPC);
        }

This script is applied both onrest and when weapons are unequipped, because we give an AC bonus for dual weilding and using large weapons(not that uncommonly). Assassins on our server receive an AC bonus relative to int mod(learned observation of combat methods and effective ability to deflect oncoming attacks based on perceived patterns is the explanation as to why they are given an AC bonus) Both events are module defined. On the weapon unequip this check is put to remove the AC bonus for weapons being removed:

while(GetIsEffectValid(eRemove))
        {
            if(GetEffectSubType(eRemove) == SUBTYPE_EXTRAORDINARY)
            {
                if(GetEffectType(eRemove) == EFFECT_TYPE_AC_INCREASE)
                {
                    if(GetEffectDurationType(eRemove) == DURATION_TYPE_PERMANENT)
                    {
                    if(GetEffectCreator(eRemove) == oSelf) oSelf is the module here.
                    {
                        RemoveEffect(oPC,eRemove);
                    }
                    }
                }
            }
            eRemove = GetNextEffect(oPC);
        }
Now as you saw the executescript is being applied to the PC, which SHOULD transfer ownership of the effect to the PC, because the PC is defining, creating, and applying the effect. However it -doesn't-, the module is still considered the creator of the effect and between being stuck in exams and other problems I just decided to reapply the effect after weapon removal instead of figuring out what fool-proof method DOES transfer ownership. Although according to nwnlexicon(which is usually a pretty good resource) assigning an inner-include block of code with assigncommand does this, I couldn't be arsed to take the time testing it to find out until I get these real life problems out of the way.
               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
What's the point?
« Reply #8 on: February 07, 2013, 02:44:21 am »


               AssignCommand works for your purposes of defining the effect creator.
               
               

               
            

Legacy_Highv Priest

  • Full Member
  • ***
  • Posts: 170
  • Karma: +0/-0
What's the point?
« Reply #9 on: February 07, 2013, 02:57:31 am »


               ExecuteScript does give me a pretty awesome AI idea though. One of the fundamental problems of AI is that they have the tendency to run the same script for each one, if you get too complicated on AI  and there is a lot of them there, then they can exceed the TMI limit and one or even a few of them basically does nothing when that happens. However what if I cycle between 5-10 differently named scripts that essentially execute the same block of code? Allowing me to change only one AI file, but avoid the TMI problem entirely. Has anyone tested if this works?
               
               

               
            

Legacy_Squatting Monk

  • Hero Member
  • *****
  • Posts: 776
  • Karma: +0/-0
What's the point?
« Reply #10 on: February 07, 2013, 03:04:30 am »


               Maybe I don't actually understand what you're saying. If not, forgive me.

I don't think that'd help any. ExecuteScript() pauses the current script, so it doesn't reset the TMI limit (though a DelayCommanded ExecuteScript() would). Splitting the same instructions up into multiple scripts also slows things down by working against the script cache.
               
               

               
            

Legacy_Highv Priest

  • Full Member
  • ***
  • Posts: 170
  • Karma: +0/-0
What's the point?
« Reply #11 on: February 07, 2013, 03:19:23 am »


               My idea is to have 5-10 scripts that simply have ExecuteScript("highv_smart_ai", OBJECT_SELF). Yes I'm extremely vain and I put highv in the name of all my original scripts >.>. Although highv_smart_ai is being executed, it's being done so under the code block of "smart_ai_1".

My idea is to have "smart_ai_1", "smart_ai_2", "smart_ai_3", etc. All of these essentially running "highv_smart_ai" in an attempt to circumvent the TMI error being triggered from many AI triggering the same script.
               
               

               
            

Legacy_Squatting Monk

  • Hero Member
  • *****
  • Posts: 776
  • Karma: +0/-0
What's the point?
« Reply #12 on: February 07, 2013, 03:30:20 am »


               You could do it with one script like this...

// my_smart_ai.nss

void BlockOne()
{
    // Do stuff
    SetLocalInt(OBJECT_SELF, "AI_STATUS", 1);
}

void BlockTwo()
{
    // Do more stuff
    SetLocalInt(OBJECT_SELF, "AI_STATUS", 2);
}

void main()
{
    switch (GetLocalInt(OBJECT_SELF, "AI_STATUS"))
    {
        case 0: BlockOne(); break;
        case 1: BlockTwo(); break;
        default:
            DeleteLocalInt(OBJECT_SELF, "AI_STATUS");
            return;
    }

    DelayCommand(0.01, ExecuteScript("my_smart_ai", OBJECT_SELF));
}

               
               

               


                     Modifié par Squatting Monk, 07 février 2013 - 03:31 .
                     
                  


            

Legacy_Highv Priest

  • Full Member
  • ***
  • Posts: 170
  • Karma: +0/-0
What's the point?
« Reply #13 on: February 07, 2013, 01:56:01 pm »


               That code actually does not avoid the problem. If you've ever done heavy work on your AI you'd know that TMI errors can be thrown out without infinite loops as a result of too many AI executing the same script at the exact same moment of time. You can delay script execution by a factor to fix this(I have), but doing so results in AI degradation. Example = AI1 executes his script, AI2 is about to execute his script, AI3 is waiting on AI2 to execute his script, etc. This is even further amplified when NPCs die at a point they were going to execute a script. Now you have living NPCs waiting on dead NPCs to do something '<img'> . I LOVE to advance my AI because I believe a real RPG needs REAL AI to be considered an RPG.(which is why I find Skyrim terrible) I've done 3 things to prevent these problems, but all 3 have their issues: The delay factor mentioned before(I dislike using this and tend to only use it for specific AI spawns such as kamikaze goblins), leader system(ONE AI is determining the actions of all of his subordinates, works quite well with only one issue. They need the leader to utilize their good AI. Once he's dead they are hardly better then BioWare's default AI.), and puppet system(similar to leader system, but in this case the area is puppeting all the NPCs within it, works quite well except you're executing the areas UserDefined event too much). I was hoping of utilizing a system where this can be avoided. My AI is extremely heavy(spellcasting NPCs act like normal player spell casters, they know not to use spells that do nothing because you have immunity from class/armor/magical buffs, but here is the kicker! They don't have "automatic knowledge" of you being immune from items or non spell bonuses, but they do if you have immunity from spell bonuses. If you are receiving immunity from a non-magical source they will still pretend to case a spell at you to "realize" the spell doesn't work and if it's a magical buff then they know to try to strip it and if you're completely immune no matter what they do then they know to switch to something else, AI will team up on you with the leader speaking commands the players can see which adds to realism, IE = "Counterspell the cleric! Heal the bashers!"). All of that stuff involves quick execution of many scripts and it does happen where a TMI STILL gets thrown out there at times.
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
What's the point?
« Reply #14 on: February 07, 2013, 03:42:07 pm »


               

Highv Priest wrote...

That code actually does not avoid the problem. If you've ever done heavy work on your AI you'd know that TMI errors can be thrown out without infinite loops as a result of too many AI executing the same script at the exact same moment of time.

Incorrect. Scripts fire sequentially. TMIs result from too much happening in a single script. They're also sort of beside the point, as they're easy to avoid. The larger problem is that you'll overburden your server.

My AI is extremely heavy(spellcasting NPCs act like normal player spell casters, they know not to use spells that do nothing because you have immunity from class/armor/magical buffs, but here is the kicker! They don't have "automatic knowledge" of you being immune from items or non spell bonuses, but they do if you have immunity from spell bonuses. If you are receiving immunity from a non-magical source they will still pretend to case a spell at you to "realize" the spell doesn't work and if it's a magical buff then they know to try to strip it and if you're completely immune no matter what they do then they know to switch to something else, AI will team up on you with the leader speaking commands the players can see which adds to realism, IE = "Counterspell the cleric! Heal the bashers!").

What are you using, custom ai, or j_ai? If j_ai, or something based on it, run it through the NWNTX compiler, or any non-standard bioware compiler. They'll pick up script errors - mostly declaration/function mismatches - that the default compiler misses. The main cause of TMIs with j_ai, however, is the execessive looping he does to optimize behavior. He made too many trade-offs of efficiency for the ability to micromanage. The more creatures you have present in combat, the more the loops iterate, and the more likely you'll get TMIs. That, not simultaneous execution, is why you see a correlation between number of spawns and TMIs.

All of that stuff involves quick execution of many scripts and it does happen where a TMI STILL gets thrown out there at times.

That's because your 'fix' does nothing to address the TMIs.

[Edit] By the by, we found x2's ai to be a fair bit meaner than j_ai in many cases, though we still use a modded j_ai here and there. We had to add a lot of spell behaviors though, and not just for our custom spells.

Funky
               
               

               


                     Modifié par FunkySwerve, 07 février 2013 - 03:55 .