Aller au contenu

Photo

What's the point?


  • Veuillez vous connecter pour répondre
60 réponses à ce sujet

#1
Highv Priest

Highv Priest
  • Members
  • 93 messages
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?

#2
Shadooow

Shadooow
  • Members
  • 4 465 messages

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

#3
Highv Priest

Highv Priest
  • Members
  • 93 messages
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.

#4
Shadooow

Shadooow
  • Members
  • 4 465 messages

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.

#5
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

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

#6
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages

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.  

#7
Highv Priest

Highv Priest
  • Members
  • 93 messages
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.

#8
Highv Priest

Highv Priest
  • Members
  • 93 messages

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.

#9
henesua

henesua
  • Members
  • 3 858 messages
AssignCommand works for your purposes of defining the effect creator.

#10
Highv Priest

Highv Priest
  • Members
  • 93 messages
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?

#11
Squatting Monk

Squatting Monk
  • Members
  • 444 messages
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.

#12
Highv Priest

Highv Priest
  • Members
  • 93 messages
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.

#13
Squatting Monk

Squatting Monk
  • Members
  • 444 messages
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 .


#14
Highv Priest

Highv Priest
  • Members
  • 93 messages
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 :D . 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.

#15
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

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 .


#16
Highv Priest

Highv Priest
  • Members
  • 93 messages
Even with 30 players actively moving around with multiple things running I've never had my server exceed 40% processor(though ocassionally it will super jump if a bunch of players are bouncing around eachother on walls,etc. which happens from pathfinding calls to the extreme degree). My AI doesn't overburden anything, but the TMI's will get thrown out anyway and I've actually done tests on this to find out. I used my poor processing laptop and spawned 200 of them in a single room, my computer never locked up despite the fact they are all trying to kill me and I'm spamming mantle scrolls and otherwise like crazy and this is with my laptop running BOTH the client and the server part instead of just a server which only deals with calculations.

I know you consider me some jackass, but lets pretend for a second I've taken programming classes and know SOMETHING about nwn(though certainly not everything). The TMI errors result purely from too many of the NPCs executing the same script without regards to the script being executed. I tested this out by having 200 NPCs execute one script that simply had them count to 100(using a while loop). One of them could do it easily, 40 of them still no issue, 100 of them still not a problem, but once I reached about 150 of them doing it the system immediately put out a TMI error.

#17
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

I know you consider me some jackass, but lets pretend for a second I've taken programming classes and know SOMETHING about nwn(though certainly not everything).

Ok, we can pretend that if you like. It doesn't change the fact that that the TMI limit is a per-script limit, or that nwn executes scripts serially, not in parallel.

The TMI errors result purely from too many of the NPCs executing the same script without regards to the script being executed. I tested this out by having 200 NPCs execute one script that simply had them count to 100(using a while loop). One of them could do it easily, 40 of them still no issue, 100 of them still not a problem, but once I reached about 150 of them doing it the system immediately put out a TMI error.


What that test indicates is that each execution is doing something that creates more work for later executions. Could be any number of things - anything that causes loops to iterate more.

Funky

Modifié par FunkySwerve, 07 février 2013 - 05:06 .


#18
ffbj

ffbj
  • Members
  • 593 messages
For the dead guys you can just put in a line at the top of the script if GetIsDead ClearAllActions TRUE return; in there on hb or combat rd end.
As far as TMI could be various causes. Clearly many npc's trying to do something at the same time can cause lag, but it is not necessarily a TMI problem. I mean to say you can get a TMI with just a single individual script that simply has Too Many Instructions. I am no expert in this area, but my scripting is sufficiently poor to have dealt with a number of TMI's over the years and it never involved just the number of npc's running the script. Although it does seem resonable to suppose with almost 200 individuals running a script you will have problems. Imo the game is just not designed to have that many individuals running scripts. I had bats spawning continuously once due to overlooking having the spawning script discontinue operation if no PC was in the area. So there were around 200 bats fluttering around, lagged like Heck, but no TMI.
Regarding the facility of execute script: I suppose that question has been answered in that it is extremely useful, for any number of occassions.
In reference to your not wanting to sound arrogant. I would not worry about that too much as almost everyone here does that, and certainly they do not appologise for it.

As far as the ai goes I have done quite a bit with that.  I think it is important to differentiate how the ai acts.  Thus they have cowardly, ambusher, archer, stealth, etc.. though the standard ai is woefully inadequate, the approach is valid. Thus I have some monsters that are more aggressive, more intelligent, etc.. so they act differently. Actually my more intelligent monsters are less aggressive, which is logical though not always.  So rather than try to the make the ai smart I merely give the more intelligent creatures more choices, which in some ways emulates intelligence as the player is less likely to be able to predict the behaviors of monsters or encounters with complete accuracy, thus making the ai seem intelligent.  If you catch my meaning, if you get my drift.
Cheers!

Modifié par ffbj, 07 février 2013 - 06:03 .


#19
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

ffbj wrote...

As far as TMI could be various causes.

No, it can't, not in the way he means. It's a very specific error put in by Bioware to halt execution of a script that exceeds 0x20000 instructions. It's an acronym for Too Many Instructions, and is meant as a last-resort block to prevent a runaway script from crashing a server. Acaos actually make a plugin for us that can lift the limit as high as 8 million, which you can find on the NWNX boards. It bears exactly zero relation to number of scripts executing at the same time, because nwn scripts cannot execute at the same time. The only thing in NWN that doesn't block is writes to bicfiles.

People tend to treat TMI as some kind of generic 'script overload' error. It isn't, though it's certainly possible to both overload your server to the point of crashing while firing TMIs.

Funky

Modifié par FunkySwerve, 07 février 2013 - 06:10 .


#20
ffbj

ffbj
  • Members
  • 593 messages
I neant in the same way as you said this:
Could be any number of things - anything that causes loops to iterate more.

But I think what he was saying in this specific instance reagarding the 200 creatures was that a TMI was caused because there were too many creatures, while the script(s) that where running would not cause a TMI otherwise, and I can see that happening.

Modifié par ffbj, 07 février 2013 - 06:09 .


#21
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

ffbj wrote...

I neant in the same way as you said this:
Could be any number of things - anything that causes loops to iterate more.

Gotcha, thanks for the clarification.

Funky

#22
Highv Priest

Highv Priest
  • Members
  • 93 messages
Is there a way I could determine whether or not nwn is capable of executing scripts at the same time? The reason I "know"(and I mean that in a non-narcissistic way) that nwn executes scripts at the same time develops from ONLY using OnHeartbeat for a spawn. A LONG time ago I decided "Why do all of these events need to be used on NPCs who have no use for them?", so I decided to simply give them there -very- simple instructions from a single heartbeat event. This was a POOR decision. The very simple heartbeat event(which literally was checking only if they had an attack target and if they didn't to attack) ACTUALLY caused lag! I then discovered if I were to delay the execution of said "heartbeat"(turned it into a pseudo-heartbeat at this point with each one executing at different times based on when they spawned) that the lag stopped and no issues resolved later.(I later decided to instead puppet them using the area, but that's besides the point)
So if nwn doesn't execute scripts at the same time, why would 40 very simple heartbeats cause massive lag when 40 very slightly differently timed heartbeats would not.(the delay was between .01 to .40 based on their objecttostring ID in relation to the first).

#23
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

ffbj wrote...
But I think what he was saying in this specific instance reagarding the 200 creatures was that a TMI was caused because there were too many creatures, while the script(s) that where running would not cause a TMI otherwise, and I can see that happening.


Sigh at dueling edits. :P No, that was MY explanation. This is what he said, and it is not possible:

Highv Priest wrote...
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.


Funky

#24
ffbj

ffbj
  • Members
  • 593 messages
Np, and thanks for the definition. I wonder why sometimes I get a script that will TMI, it's a spawn script, but not always. I'd say about 1 out of 5 times it fires it TMI's. Well here it is:

#include "NW_I0_GENERIC"
#include "x3_inc_string"
void main()
{
int nCreature, nNumber;
string sCreature = "";
string sMessage = (StringToRGBString("\\n AMBUSH !! \\n",STRING_COLOR_RED));
object oPC = GetEnteringObject();
object oArea = GetArea(oPC);
if (!GetIsPC(oPC))
return;

if (GetLocalInt(oPC,"Safe") == 1)
return;

if (GetLocalInt(oPC,"Ran") > 2 +d4(1))
return;//script will only run 3 times,plus d4 default would be 4
int iHd = GetHitDice(oPC);
if (d100() < 60 + (iHd))//set between 50-75. Lower means fewer spawns.
return;

//if (GetDetectMode(oPC) == DETECT_MODE_PASSIVE)//not actively searching.
//return;

object oTarget = oPC;
float fDistance = 2.0 + (iHd/2);//higher level notice things futher away
location lWP = GetRandomLocation(oArea,oTarget,fDistance);
location lTarget = GetLocation(oTarget);

//Cycle through PCs in area

object oPChp = GetFirstPC();
while (GetIsObjectValid(oPChp)||(oArea == GetArea(oPChp)))
{
if (oArea != GetArea(oPChp))
{
oPChp = GetNextPC();
}
iHd++;
iHd += GetHitDice(oPChp); //trying to add the pc's in the area hp
oPChp = GetNextPC();
}

if (iHd < 10)
SetLocalInt(oPC,"Spawn",1);
else if (iHd < 20)
SetLocalInt(oPC,"Spawn",2);
else if (iHd < 30)
SetLocalInt(oPC,"Spawn",3);
else
SetLocalInt(oPC,"Spawn",4);

if (GetIsNight())
{
SetLocalInt(oPC,"Spawn", GetLocalInt(oPC,"Spawn")+1);
}

if ( GetStealthMode( oPC ) == STEALTH_MODE_ACTIVATED )
{
SetLocalInt(oPC,"Spawn", GetLocalInt(oPC,"Spawn")-1);
}

if ((GetLocalInt(oPC,"Spawn")== 1) && (GetIsSkillSuccessful(oPC, SKILL_LISTEN, 5)))//easy

{
SendMessageToPC(oPC, "You Hear Something!");
GiveXPToCreature(oPC, 25);

switch (d4())
{
case 1: sCreature = "thugcn"; break;//resref of creature
case 2: sCreature = "madcrow"; break;
case 3: sCreature = "townperson"; break;
case 4: sCreature = "monkbd"; break;
}
for (nNumber = 0; nNumber < d3(); nNumber++)
CreateObject(OBJECT_TYPE_CREATURE, sCreature, lWP, FALSE);

object oCreature = GetNearestObjectToLocation(OBJECT_TYPE_CREATURE, lWP);
float fDistance1 = GetDistanceBetweenLocations(lWP, lTarget);
string sDistance = FloatToString(fDistance1, 2,0);
DelayCommand (3.0,SendMessageToPC(oPC, " Some noise, over there! " + sDistance + " metres away."));
DelayCommand (6.0, TurnToFaceObject(oCreature, oPC));
}


if ((GetLocalInt(oPC,"Spawn")== 2) && (GetIsSkillSuccessful(oPC, SKILL_SPOT, 15)))//harder
{
SendMessageToPC(oPC, "You Spot Something!");
GiveXPToCreature(oPC, 50);
switch (d4())//should be tougher creatures below
{
case 1: sCreature = "thugcn"; break;//resref of creature
case 2: sCreature = "monkbd"; break;
case 3: sCreature = "townperson"; break;
case 4: sCreature = "rioter003"; break;
}


//Randomly generate the number of monsters//
for (nNumber = 0; nNumber < d4(); nNumber++)//change d4 for more
CreateObject(OBJECT_TYPE_CREATURE, sCreature, lWP, FALSE);
object oCreature = GetNearestObjectToLocation(OBJECT_TYPE_CREATURE, lWP);
float fDistance1 = GetDistanceBetweenLocations(lWP, lTarget);
string sDistance = FloatToString(fDistance1, 2,0);
DelayCommand (2.5,SendMessageToPC(oPC, " Something, over there! " + sDistance + " meters away."));
DelayCommand (5.0, TurnToFaceObject(oCreature, oPC));
}
else if ((GetStealthMode(oPC) == STEALTH_MODE_DISABLED) && (GetIsNight ()) && (GetLocalInt(oPC,"Ran") < 3))
{
CreateObject(OBJECT_TYPE_CREATURE, "robber", lWP, FALSE);
DelayCommand (2.0, FloatingTextStringOnCreature(sMessage, oPC, FALSE));

}


if ((GetLocalInt(oPC,"Spawn")==3) && (GetIsSkillSuccessful(oPC, SKILL_LISTEN, 20)))

{
SendMessageToPC(oPC, "A faint sound reaches you!");
GiveXPToCreature(oPC, 100);
switch (d6())
{
case 1: sCreature = "thugcn"; break;//resref of creature
case 2: sCreature = "rioter003"; break;
case 3: sCreature = "townperson"; break;
case 4: sCreature = "youth01"; break;
case 5: sCreature = "maddog"; break;
case 6: sCreature = "monkbd"; break;
}
for (nNumber = 0; nNumber < d6(); nNumber++)
CreateObject(OBJECT_TYPE_CREATURE, sCreature, lWP, FALSE);
object oCreature = GetNearestObjectToLocation(OBJECT_TYPE_CREATURE, lWP);
float fDistance1 = GetDistanceBetweenLocations(lWP, lTarget);
string sDistance = FloatToString(fDistance1, 2,0);
DelayCommand (1.5,SendMessageToPC(oPC, " Something is moving, over there! " + sDistance + " metres away."));
DelayCommand (4.0, TurnToFaceObject(oCreature, oPC));
}

if ((GetLocalInt(oPC,"Spawn") == 4) && (GetIsSkillSuccessful(oPC, SKILL_SPOT, 25)))//etc...

{
SendMessageToPC(oPC, "Your highly attuned senses are alerted!");
GiveXPToCreature(oPC, 150);
switch (d6())//should be tougher creatures
{
case 1: sCreature = "harpy"; break;//resref of creature
case 2: sCreature = "robber"; break;
case 3: sCreature = "townperson"; break;
case 4: sCreature = "skeletondart"; break;
case 5: sCreature = "maddog"; break;
case 6: sCreature = "skeletonrapier"; break;
}
for (nNumber = 0; nNumber < d4(); nNumber++)
CreateObject(OBJECT_TYPE_CREATURE, sCreature, lWP, FALSE);
object oCreature = GetNearestObjectToLocation(OBJECT_TYPE_CREATURE, lWP);
float fDistance1 = GetDistanceBetweenLocations(lWP, lTarget);
string sDistance = FloatToString(fDistance1, 2,0);
DelayCommand (1.0,SendMessageToPC(oPC, " Some movement, over there! " + sDistance + " metres away."));
DelayCommand (4.0, TurnToFaceObject(oCreature, oPC));
}

else if ((GetStealthMode(oPC) == STEALTH_MODE_DISABLED) && (GetIsNight ()) && (GetLocalInt(oPC,"Ran") < 3))
{
CreateObject(OBJECT_TYPE_CREATURE, "robber", lWP, FALSE);
DelayCommand (2.0, FloatingTextStringOnCreature(sMessage, oPC, FALSE));

}
else if ((GetStealthMode(oPC) == STEALTH_MODE_DISABLED) && (GetIsNight ()) && (GetLocalInt(oPC,"Ran") < 3))
{
CreateObject(OBJECT_TYPE_CREATURE, "harpy", lWP, FALSE);
DelayCommand (2.0, FloatingTextStringOnCreature(sMessage, oPC, FALSE));

}


SetLocalInt(oArea,"Ran", GetLocalInt(oArea,"Ran")+1);
DelayCommand (400.0/ d4(2), ExecuteScript("ranspslistenslum",oArea));
DelayCommand (300.0, DeleteLocalInt(oArea,"Ran"));

}


I have many similar scripts that work fine but this one seems to be cranky. Maybe I just have a bracket in the wrong place. Now that I look at it maybe these lines have an uneeded bracket:

{
if (oArea != GetArea(oPChp))
//{maybe no bracket needed here.
oPChp = GetNextPC();
}

Modifié par ffbj, 07 février 2013 - 06:24 .


#25
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

Highv Priest wrote...

Is there a way I could determine whether or not nwn is capable of executing scripts at the same time? \\

Sure. You can read the forums and listen to people who know. Or ask people who have worked on the engine and know. Or you could use the Omnibus to look for posts by people who put the engine together and know. You could also devise an experiment designed to execute two scripts at the exact same instant to see for yourself, but clearly your experimentation thus far has only lead you astray, so you'd have to be a little more rigorous. That's not an indirect knock at you, by the way - just yesterday I managed to convince myself that the custom SetEffectCreator NWNX function was only returning a valid creator until the end of that script - because I wasn't rigorous enough in my testing, and didn't insert enough debug code to figure out what was really going on.

So if nwn doesn't execute scripts at the same time, why would 40 very simple heartbeats cause massive lag when 40 very slightly differently timed heartbeats would not.(the delay was between .01 to .40 based on their objecttostring ID in relation to the first).

Because the engine can only do so much in a given amount of time. Saying that scripts execute serially is not saying you can't jam up your server with too much code - it still takes time to execute, and some individual instructions can cause massive lag spikes, like object creation, because they require a lot of other things to happen. I won't pretend much expertise when it comes to computers, but I'm absolutely certain about this, and I have neither the time nor inclination to shoot down every anecdotal scrap of evidence you've accumulated in support of your erroneous conclusion. Virusman could probably give you a more technical explanation, if you're curious - he's dealt with the game clock, among many other things.

If it's any comfort, I made the same mistake, way back when, not understanding serial execution or blocking. It grants a lot of clarity once you do, and allows you to figure out more of what's really going on. I'll see if I can hunt down GZ on the omnibus talking about it, if I can do so quickly.

Funky