Aller au contenu

Photo

Creature AI - Requires Whole Process Explanation Please. (Expert Advice Required.)


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

#1
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi All,

This all started when I was trying to understand creatures using their special abilities in combat, but after trying to look through the various scripts and different includes I find I am in need of some guidance as to what is actually going on.

Here is what I have learned to date, and would like some clarification on please. Also, please correct me if I have made any errors in my assumptions.

1) The default end of round script for a creature is nw_c2_default3. In this, it checks for GetBehaviorState and GetSpawnInCondition constants. Yet, when I look at the default OnSpawn nw_c2_default9, there does not appear to be any of these flags set. Therefore, is it the HenchDetermineCombatRound function that fires in every case?

2) I did look for scripts that might set some of the above constants and discovered nw_c2_herbivore and nw_c2_omnivore OnSpawn scripts. Are these scripts ever used anywhere anymore, or are they part of some redundant code? Because if they were placed on a creature, then this would make the default nw_c2_default3 behave differently wouldn't it?

3) Are there any creatures that have these alternative OnSpawn scripts assigned or variables added?

4) When HenchDetermineCombatRound fires, it checks to see if an alternative AI Script has been attached to the creature within "AIScript" and fires it if present. If not, it fires the default script hench_o0_ai - after doing a number of other checks and state setting. Is this correct?

5) The default AI script hench_o0_ai seems insanely complicated (to be expected really). Posted Image And this is where it gets too complicated for me to fathom. Posted Image For instance, there are checks like SpecialTacticsCowardly and checks like if (GetLocalInt(OBJECT_SELF, henchHealCountStr)), which obviously help determine how this creature responds with tactics during combat. My question is, where are these states set? I would have thought they may have been set in the creature's OnSpawn or something like that, but I do not know.

6) Further down the default AI script, there is another reference to yet another alternative script to fire with the line string sSpecialAI = GetLocalString(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT"); It mentions checking the x2_ai_behold script for details. However, why is there this alternative AI script in addition to the one that gets fired in 4 above? Why does this alternative script get picked up here rather than in the above pick up?

7) I have no idea how to read this line: SetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE, iCheckHealing ? HENCH_HEAL_SELF_CANT : HENCH_HEAL_SELF_WAIT); At least, I  understand up to the bit that says iCheckHealing ? HENCH_HEAL_SELF_CANT : HENCH_HEAL_SELF_WAIT. How does this bit of code read? I assume it somehow translates into something that would return a single integer. But how would you say it long hand?

8) Again, how does this bit of code read: gbSpellInfoCastMask = HENCH_SPELL_INFO_UNLIMITED_FLAG | HENCH_SPELL_INFO_HEAL_OR_CURE; Is gbSpellInfoCastMask a function or an integer? How does the line of code read long hand in either case?

The bottom line is, can someone break down the run of events that lead a creature to take certain actions in combat according to the code as laid out here, or how it actually runs if I have made an error in the above assumptions.

I recognise that I am asking a lot here, and may not get any response unless it is spotted by someone who has a good grasp of scripting creature AI. I can but hope ...

And if I do grasp this, then maybe I can begin to understand a little more about altering creature combat AI. Posted Image

Many thanks in advance.

Lance.

Modifié par Lance Botelle, 19 mars 2011 - 11:15 .


#2
kamalpoe

kamalpoe
  • Members
  • 711 messages
Join irc and talk to brianmeyerdesign aka pain

#3
kevL

kevL
  • Members
  • 4 052 messages

Lance Botelle wrote...

7) I have no idea how to read this line: SetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE, iCheckHealing ? HENCH_HEAL_SELF_CANT : HENCH_HEAL_SELF_WAIT); At least, I  understand up to the bit that says iCheckHealing ? HENCH_HEAL_SELF_CANT : HENCH_HEAL_SELF_WAIT. How does this bit of code read? I assume it somehow translates into something that would return a single integer. But how would you say it long hand?

i know, i know! i think .. it means

if (iCheckHealing) SetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE, HENCH_HEAL_SELF_CANT);
else SetLocalInt(OBJECT_SELF, HENCH_HEAL_SELF_STATE, HENCH_HEAL_SELF_WAIT);

8) Again, how does this bit of code read: gbSpellInfoCastMask = HENCH_SPELL_INFO_UNLIMITED_FLAG | HENCH_SPELL_INFO_HEAL_OR_CURE; Is gbSpellInfoCastMask a function or an integer? How does the line of code read long hand in either case?

gbSpellInfoCastMask is an Int (see hench_i0_spells)

I think the | means (less sure on this one), roughly Or : as in, if (HENCH_SPELL_INFO_UNLIMITED_FLAG == TRUE) or (HENCH_SPELL_INFO_HEAL_OR_CURE == TRUE), gbSpellInfoCastMask = TRUE.

i've noticed some other funky shortcuts like ' nCurrentCond & ~nCondition ' which might mean (nCurrentCond ANDNOT nCondition) ((but then they stick a ! out in front and my mind starts spinning))

My question is, where are these states set?

tell ya, what I'm doing, 'cause I've been working on some of this stuff myself (Pain had it right, "goto spaghetti"). I've taken all the scripts from x0, x1, x2 (just the .nss files) and thrown them into respective subdirectories of a temp_scripts folder. Then I use an app like HDSearch that lets one search for text strings within all those files rather quickly. It works pretty good for hunting down constants and functions (and takes some load off the TS, also, by using an external text-viewer like ConTEXT or Crimson Editor)

The bottom line is, can someone break down the run of events that lead a creature to take certain actions in ...

uhm, no.

#4
Lugaid of the Red Stripes

Lugaid of the Red Stripes
  • Members
  • 955 messages
The spawn-in conditions are a legacy from NWN1, and can be reactivated with a custom spawn script.

#5
painofdungeoneternal

painofdungeoneternal
  • Members
  • 1 799 messages
The spawn in conditions are still in use, there is a master variable the ai uses and checks related to that. It is probably one of the most important components of the current AI system.

It is controlled by the functions inside "x0_i0_spawncond.nss"
SetSpawnInCondition and GetSpawnInCondition

and to cause fast buffing you just use
SetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY) ( generally in the on spawned script )

but those just set a bit on the variable "NW_GENERIC_MASTER" which you can set in the toolset bypassing all scripts. I am actually fixing some things developed by the community to actually use those variables, Akavit did a feature to make creatures properly flee, and other major flags, like things related to team type AI's and the like.

The following bits are what you can set to the variable, all expressed as hex values:
const int NW_FLAG_SPECIAL_CONVERSATION = 0x00000001;
const int NW_FLAG_SHOUT_ATTACK_MY_TARGET = 0x00000002;
const int NW_FLAG_STEALTH = 0x00000004;
const int NW_FLAG_SEARCH = 0x00000008;
const int NW_FLAG_SET_WARNINGS = 0x00000010;
const int NW_FLAG_ESCAPE_RETURN = 0x00000020; //Failed
const int NW_FLAG_ESCAPE_LEAVE = 0x00000040;
const int NW_FLAG_TELEPORT_RETURN = 0x00000080; //Failed
const int NW_FLAG_TELEPORT_LEAVE = 0x00000100;
const int NW_FLAG_PERCIEVE_EVENT = 0x00000200;
const int NW_FLAG_ATTACK_EVENT = 0x00000400;
const int NW_FLAG_DAMAGED_EVENT = 0x00000800;
const int NW_FLAG_SPELL_CAST_AT_EVENT = 0x00001000;
const int NW_FLAG_DISTURBED_EVENT = 0x00002000;
const int NW_FLAG_END_COMBAT_ROUND_EVENT = 0x00004000;
const int NW_FLAG_ON_DIALOGUE_EVENT = 0x00008000;
const int NW_FLAG_RESTED_EVENT = 0x00010000;
const int NW_FLAG_DEATH_EVENT = 0x00020000;
const int NW_FLAG_SPECIAL_COMBAT_CONVERSATION = 0x00040000;
const int NW_FLAG_AMBIENT_ANIMATIONS = 0x00080000;
const int NW_FLAG_HEARTBEAT_EVENT = 0x00100000;
const int NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS = 0x00200000;
const int NW_FLAG_DAY_NIGHT_POSTING = 0x00400000;
const int NW_FLAG_AMBIENT_ANIMATIONS_AVIAN = 0x00800000;
const int NW_FLAG_APPEAR_SPAWN_IN_ANIMATION = 0x01000000;
const int NW_FLAG_SLEEPING_AT_NIGHT = 0x02000000;
const int NW_FLAG_FAST_BUFF_ENEMY = 0x04000000;

( the above values are in the same number system called hex that metamagic and spell targets use, or that websites write their colors in where it's numbered from 0-F instead of from 1 to 9, you can easily find an online calculator to turn a hex number to an integer, each one of those above actually is a single bit )

The language related to the operators you asked about like & and |, in which you illustrated the following line:

gbSpellInfoCastMask = HENCH_SPELL_INFO_UNLIMITED_FLAG | HENCH_SPELL_INFO_HEAL_OR_CURE; 

Uses those bits, and those are bitwise operators. Please read this on Bitwise math operators where i described it all. The above merges two variables, and later on after you do a |, you can then use a & inside an if to see if those constants are inside the single variable. It's just a way to store a boolean true false into a integer - if you think of the following binary string 0011, that is the number 3, but it's also FALSE, FALSE, TRUE, TRUE. If i take the following psedo code which i will write the number in binary...


int iMask1 = 1; // binary 0001 - this squares up each number
int iMask2 = 2; // binary 0010;
int iMask3 = 4; // binary 0100;

int iMask4 = iMask1 | iMask2; // this is an "or", which makes 0001 and 0010 into 0011;

if ( iMask4 & iMask2 ) // this uses a bitwise "and"  which evaluates to be true since iMask2 was put into iMask4 and 0010 is kind of a special mask allowing you to see if just that bit is true or false in the first number.
{
   // do stuff
}

if ( iMask4 & iMask3 ) // this evaluates to be false since iMask3 was not put into iMask4
{
   // do stuff
}


You really need to study this though, as it's pretty in depth.

gbSpellInfoCastMask is a global variable, which means its defined outside of main which means it's always in effect. This is done like crazy in the AI and it makes it very hard to keep track of the variables.

To answer questions about the flow of the AI, well i added SendMessageToPC each time any function is used, and i can say each script uses almost EVERY function in the ai at least once, and it causes a TMI due to the sheer quantity it's trying to echo and noticeable lag as it tries to catch up, i am talking about it scrolling quickly past on the screen. The only term for it is spagetti,and unlike other code, it uses those global vars to affect things completely unrelated to what is inside the current function. I have read and traced the code line by line throughout the entire process, and after about the 20th function deep my brain dumps where it was before.

I have removed a LOT of the garbage involved though, in the code i linked to.

The issues are not really in the code, the issue is one of fixing the caches the AI uses. One of the core pieces in henchspells.2da which describes a spell for the AI, so it knows how each spell or ability works. The other 2da's provided do class abilities. Fixing these ( and these happen to be full of bitwise packed integers ) is really a key issue to getting it working better. As such once my editor is finished, it should allow you to really correct a LOT of the issues without needing a computer science degree.

The other thing that ( i think ) needs to be done, is to have a 2da which correlates resrefs to spell abilities, which uses some sort of toolset plugin to export those. This is something which is dependent on another project using Grinning Fools creature creator, which has a tool for saving resref to a database. This would provide a "cache" for looking up those special abilties, spells and feats, and for you to set what those are quickly especially if a toolset plugin lets you bypass the engine limitation of not allowing us access to those. This is basically something i am designing at this moment for my revamp of the AI system, not something required but it would really solve a lot of issues.

The functions most people edit to put their fixes in, tend to just make it all much more complicated, even the AI itself has multiple redundant functions that work the exact same way since the dev's just did not realize that a certain feature was already implemented. The spawn conditions master variable, the caching of items, feats and spells each round, and the lookup 2da's are all core foundations upon which the AI rests - and why it's using up so much CPU as it trys to brute force guess what abilities a creature has from round to round. Quite of the other stuff is just making how it works overly complicated.

#6
kevL

kevL
  • Members
  • 4 052 messages
thanks for the bitwise lesson, Pain. ' bit ' was wonderful ..

#7
GhostOfGod

GhostOfGod
  • Members
  • 863 messages
Here's a link to the Lexicon with a bit of info on the ternary operator "?". Just in case you wanted to look it over:
www.nwnlexicon.com/compiled/primer.ternaryconditional.html

#8
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi Everybody,

Many thanks for this feedback, and ...

KevL ... For your explanation of the functions.

GhostofGod ... For the link to a later copy of the Lexicon than I was currently using.

Pain ... For a most interesting and in-deth post that raises many new questions. Posted Image ...

From what I have seen and heard of some of the code explanations, I see I much prefer it when code is written out long hand, as I can follow it reasonably well then. When a few short cuts are thrown into the equation (literally), then I find I am struggling to understand the single line to the point where it throws my train of thought up to that point. It's like throwing even more spaghetti onto the spagahetti! Posted Image

I especially appreciate the link to the bitwise math flags, which I have never seen explained before, and will spend some time looking at and trying to digest. (I have made a seperate post with questions below.)

I also really appreciate you telling me that you have added SendMessageToPC lines to the code to try to understand what is going on and what conclusions you have drawn to date. This is exactly the kind of thing I would have done if I ever had the time and understanding of what and where to place them with respect to the many includes involved.

I think you hit the nail on the head when you point out that there is a lot of redundant/junk code that only contributes towards confusing the issue of what is actually happening. I think knowing the full process of a combat round, including which scripts are involved (and include files) will be the first stage of understanding - for me especially. In other words, which include files (or onspawn scripts) can be ignored and which ones are the important ones.

Is it only the "NW_GENERIC_MASTER" that is updated and handles monster AI now then - using the constants you list?

And from a mediocre scripter's perspective (which I judge mine to be), I am still unclear when and where these constants added to the "NW_GENERIC_MASTER" actually get interpreted and carried out. Forgive my ignorance, but here is an  example of what I am trying to say ...

E.g. Let's say I do add the line SetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY); within a creature's OnSpawn script , on the understanding that this is the way controlling monster AI is "supposed to be done" within NWN2 among all the different scripts flying around the game.

Here are my questions ...

1) How do we find out what/how these flags actually control our creatures? From this one variable setting, I can only glean the impression that my creature will buff - "speedily" apparantly - but this does not explain the conditions it has to meet (does it even) to perform said buffing. And what is "fast" buffing as opposed to "slow" buffing - if there is such a thing?

2) Where is the code that would show me exactly what is happening? i.e Is there an include where having the spawn in flag condition set as a "fast buffer" would run a "FastBuff" function somewhere? (This is how I have always understood coding, but this AI coding appears to break away from my current understanding.) In my search, I have not found a clear or concise function that actually relates to any of these conditions that are set. Is there an include I have missed somewhere? ... I have searched though many, but got buried under the spaghetti. Posted Image

3) Are these flags mutually exclusive, or can they all/some be set on the same creature? Some, by their very name would suggest they cannot have both placed on the creature, but others would imply adding different ones might be possible. How would we know which ones are or are not exclusive from one another. (I would like to see some code that confirmed more than common sense.)

Global Variables
===========

Later on you  mention that "gbSpellInfoCastMask is a global variable". Could you explain this a little more please. As I thought I understood it, a global variable still needed to be defined somewhere ... is this not the case? In my search for gbSpellInfoCastMask, I did not see anything like int gbSpellInfoCastMask or const int gbSpellInfoCastMask, which I thought were how any global variables were defined. Does the gb part in front of the rest of the variable name some how register is as a global one rather than act as some naming format like we do (but do not have to obey) with things like sName for a string variable? In other words, I do not have to place the s in front of a string name, but is the gb specific and required in its forming - and is how it is recognised as a global? Basically, where is this global variable initially decalred/defined?

I will also try looking around your website, in the hopes of learning more here.

Weighting
=======

In my general observations, I have seen references to weightings, implying a leaning towards certain behaviours according to circumstances ... yes? However, the implementation of this (again) appears eclectic and hard to track down. And while I can understand the idea of giving different tactics different "weights", I have not seen any real explanation of its implementation .. like when and where different weights can be applied.

Simplified View
==========

My simplified understanding of the combat process (and creature AI) is as follows:-

1) Creature "behaviour" can be predisposed according to flags set in the creature's OnSpawn scripts.
2) During combat (and at the end of each round), the On End Combat Round fires, checking behaviour responses according to those set and current circumstances.

Therefore, I can see that variables need to be set on a creature (via on spawn) and that those variables are compared in functions at the end of each round that then determine what actions the creatures performs for the following round.

When comparisons are made, set behaviour, monster ability and circumstances must help dictate their next action. Why is there not one script (all be it perhaps complicated) that runs through a list of functions checking this procedure?

Basically, is it possible to illustrate via a diagram the processes and scripts involved (and includes) at the end of each round, or have I misunderstood the way this part of the code works?


This is how it currently looks from the highest level. I have not yet branched from the various includes following their own paths according to what a creature does as it gets too complicated and may even double back on itself.

Posted Image

Is there any reason why it cannot work like this? What processes am I missing between the two?

Posted Image


That's plenty of text for the time being ... I really do appreciate the help with this.

Many thanks.

Lance.

Modifié par Lance Botelle, 20 mars 2011 - 08:59 .


#9
kevL

kevL
  • Members
  • 4 052 messages
trying to take some of the Burden off Pain, here (and working things through fer myself)

here's a strand of pasta .. re. NW_FLAG_FAST_BUFF_ENEMY

It starts by setting the Flag in the onSpawn ( eg, nw_c2_default9 )
then in the onHeartbeat, it checks the Flag (eg. nw_c2_default1 )
if TRUE, run TalentAdvancedBuff() ( x0_i0_talent )

the function TalentAdvancedBuff() checks for & runs a whackload of ActionCastSpellAtObject() with the bInstant parameter. (Fast Buff)

The funny thing I had difficulty with is the fact that most of the AI functions return Ints, insead of Voids; what I gather is happening, however, is that by doing things that way the Devs can run actions and check for conditions all at one go, allowing the AI to run yet more actions and check for more conditions, in what is presumably a logical mess. More sauce, pls

The strand ends, I'm guesstimating, once a check fails and returns FALSE. Notice, Lance, that AI is not simply the onCombatRoundEnd script; any event can start the pasta cooking.


ps. it looks as though the ' i0 ' scripts ( *_i0_*.nss ) contain much / most / some of the AI functions, while ' o0 ' scripts seem related to AI actions (return Void instead of #include).

Like i alluded above, having all the uncompiled scripts in a readily searchable index (directory) makes hunting through them far more efficient. (tempting to make a real index of them ..) For example, that's how I found the line

int gbSpellInfoCastMask;

on line 309 of hench_i0_spells (thanks to Pain i figure the ' gb ' stands for Global Boolean) but haven't a clue how the first instance actually gets registered


lastly, if I've been redundant pls forgive - others may find it helpful.

#10
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi KevL,

Thanks for your input. It all helps ... In fact, your post highlights one of the issues I am having with recognising exactly what scripts to use and which to ignore. For instance, I will address each point below ...

kevL wrote...

trying to take some of the Burden off Pain, here (and working things through fer myself)

here's a strand of pasta .. re. NW_FLAG_FAST_BUFF_ENEMY

It starts by setting the Flag in the onSpawn ( eg, nw_c2_default9 )
then in the onHeartbeat, it checks the Flag (eg. nw_c2_default1 )
if TRUE, run TalentAdvancedBuff() ( x0_i0_talent )

the function TalentAdvancedBuff() checks for & runs a whackload of ActionCastSpellAtObject() with the bInstant parameter. (Fast Buff)


It looks as though you are looking in older versions of the default scripts. For instance, the nw_c2_default1 version I am looking at has HenchTalentAdvancedBuff instead of TalentAdvancedBuff, which is found in the hench_i0_ai include and not the x0_i0_talent that you refer to. So, my first question is, can we totally ignore the older include file you refer to now and only use the newer version of the function and newer include?

Furthermore, what happens if the creature has more than one copy of a valid spell to draw upon? E.g. If the creature has two SPELL_PREMONITION memorised, then wouldn't they simply recast the same spell if the PC is still within 40' and not yet in combat with the creature? This seems to be missing a check to see if the spell has already been cast to allow the monster to potentialy select another spell if another round passes prior to it going into combat. And at what stage does the creature move from buffing to direct combat confrontation? When and where is this check made? ACTUALLY .... scrap the above ... I just noticed the creature has the fast buff turned FALSE after the first application in the heartbeat.


kevL wrote...

The funny thing I had difficulty with is the fact that most of the AI functions return Ints, insead of Voids; what I gather is happening, however, is that by doing things that way the Devs can run actions and check for conditions all at one go, allowing the AI to run yet more actions and check for more conditions, in what is presumably a logical mess. More sauce, pls


As far as I can see, this kind of function does both the spell (if they have it) and returns an int to say if they have been successful or not. If the int returned is FALSE, then the script that called the function should carry on to determine what else the creature may try to do in this round.


kevL wrote...

The strand ends, I'm guesstimating, once a check fails and returns FALSE. Notice, Lance, that AI is not simply the onCombatRoundEnd script; any event can start the pasta cooking.


See my comment above. I think returning FALSE actually means continue to look for another action, whereas returning TRUE may be what causes it to end, because it has found an action the creature can do.

I think that if there is some other call to this code is partly what is confusing me. I mean I can see another creature calling this AI check during its combat turn, but surely nothing else needs to?


kevL wrote...

ps. it looks as though the ' i0 ' scripts ( *_i0_*.nss ) contain much / most / some of the AI functions, while ' o0 ' scripts seem related to AI actions (return Void instead of #include).

Like i alluded above, having all the uncompiled scripts in a readily searchable index (directory) makes hunting through them far more efficient. (tempting to make a real index of them ..) For example, that's how I found the line

int gbSpellInfoCastMask;


I am considering copying all the includes into named versions of my own and including/editing those versions for the AI to see what happens. E.g. The HenchDetermineCombatRound(); function would link to the function in an attached include called alb_combat_ai or something like that. As I edit more and more sub includes, I will rename them too, to  remove the junk not required.


kevL wrote...

on line 309 of hench_i0_spells (thanks to Pain i figure the ' gb ' stands for Global Boolean) but haven't a clue how the first instance actually gets registered

lastly, if I've been redundant pls forgive - others may find it helpful.


I guess we will have to wait for PAIN to answer that one if he is still around and able to answer.

As I say, your own thoughts are very much welcome and help me to understand ... keep them coming. Posted Image

Lance.

Modifié par Lance Botelle, 20 mars 2011 - 08:40 .


#11
The Fred

The Fred
  • Members
  • 2 516 messages
The problem with the AI is that it's a big pile of spaghetti code with metaphorical bolognaise piled all on top. It was bad enough in NWN1, but in NWN2 they seem to have ruthlessly implemented the bitwise C functionality which can be used in NWScript (an example of this is metamagic feat checking - in NWN1 they did things like if(nMetaMagic == METAMAGIC_EMPOWER) whereas in NWN2 they use the more correct if(nMetaMagic & METAMAGIC_EMPOWER) - however, in the AI it is used in ways it needn't be). This is very much in the vein of the "old-school" C-style save-every-line-possible coding, and as cool as it feels to write it and know that you're being clever, it's a damn pain to read it.

#12
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

The Fred wrote...

The problem with the AI is that it's a big pile of spaghetti code with metaphorical bolognaise piled all on top. It was bad enough in NWN1, but in NWN2 they seem to have ruthlessly implemented the bitwise C functionality which can be used in NWScript (an example of this is metamagic feat checking - in NWN1 they did things like if(nMetaMagic == METAMAGIC_EMPOWER) whereas in NWN2 they use the more correct if(nMetaMagic & METAMAGIC_EMPOWER) - however, in the AI it is used in ways it needn't be). This is very much in the vein of the "old-school" C-style save-every-line-possible coding, and as cool as it feels to write it and know that you're being clever, it's a damn pain to read it.


Hi The Fred,

QUOTE: "it's a damn pain to read it" - You can say that again! Posted Image

I think I will need to ask for "translations" for each of these lines if and when I delve into the code more. (If I still struggle to understand it after reading PAIN's site.)

Lance.

#13
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi PAIN,

Please can you confirm I have understood Bitwise Maths Flags by answering the following:

When referring to bits, the following signs now mean .....

a) The | sign means to add bits.
B) The & ~ can be read as, "and not those" or "without these".
c) The & sign is used to test for a flag. However, I need to clarify does it mean "contain" as opposed to "exact"?

E.g. In the following code, are we saying if ( iColorState & COLORBIT_RED ) returns TRUE? as if we were saying iColorState is made up of Red and Blue and Green, and ... if iColorState "contains" red, then return TRUE. I stress contain, because if it was checking just for Red, then it would return FALSE. From your examples on your site, then I am guessing "contains" is correct.

int iColorState = COLORBIT_RED | COLORBIT_BLUE | COLORBIT_GREEN;
if ( iColorState & COLORBIT_RED )
{
      SendMessageToPC( oPC, "This is red!");
}

d) These signs work the code in such a way to include or exclude bits from one another by calculating valid code that cannot be replaced by simple addition or subtraction of bit values, which would actually give invalid calculations. Yes?

Thanks in advance,

Lance.

#14
painofdungeoneternal

painofdungeoneternal
  • Members
  • 1 799 messages
Yes, that is a way of thinking about it which will let you bitwise math in your own code. I tried describing it that way, in a functional manner so you could use bitwise math in your own code. This is a valid way to understand it, but i will try to expand on it a bit so you can deal with what is pretty much very advanced usage of the above in the AI.

You should also learn how

iColorState = iColorState | COLORBIT_BLUE;
is the same as
iColorState |= COLORBIT_BLUE;

just like
sMessage += " frog"

appends things to a string. There are ways of doing that with type of operation.

You should use an online calculator that lets you see bits easily and put in the various values and see what it is doing, and compare that to actual code.

a) it adds bits because is combines two sets of binary bits so that if 0 or 0, it does 0,in all other cases it does a 1. That is the bitwise or operator, remember that double || is the or in if statements, if anything is true its true and it only does false if everything is false.

B) The ~ is basically not, it inverts bits so 01010 becomes 10101, if it's one it becomes zero and vice versa.

c) The & is the "and" operator, it says if a 1 and a 1, it is a 1, in all other cases a 0. Notice that double && in if statements is an and. It requires both to be true to be true but at a bitwise level.

d) yes that is how it does simple logic. It is asking if ( lets assume ) the third bit, which is 0100, it looks to see if that is present in the second string. If the second value is 0000, 1011, 1000, 1010, 0011, 0010 it returns false. If it's 0100, 1111, 1100, 1110, 0111, 0110 it returns true. When i say it contains, it contains a 1 in that 3rd position ( from the right ) of the binary values, and the "or" | operator is a way to add a 1 into that 3rd position. The constants just make it easier to read in english.

The AI uses the shift operators as well, for << and >>. These guys when you see them take the bits and shift them to the left or right respectively. This means if you have a 0001 and shift left it becomes 0010, shift left 2 becomes 0100, shift left 3 becomes 1000. Shift right moves them the other way. This probably is not as important to learn at first, but you need to know it's doing this when you see it.

Another thing he does a lot is a mask, where he takes a value like 111110 and uses the "and" operator to remove a value. If you see a constant with the name _MASK this is what he is using it for. ( generally for the packed bits from henchspells.2da.

The gb* variables are defined in the libraries, there is no spot they are initialized. There is no simple beginning to the AI, and they get redefined constantly with the left hand having no idea what the right hand is doing. Where in the code, good luck finding that out, you basically have to sendmessagetopc while it runs to figure out what it's doing as it's not like working with spell scripts.

If you were to use my version, i have them ALL inside "_SCInclude_AI_c.nss" and all the specifically ai functions in "_SCInclude_AI.nss". Might be easier to trace things down since i have untangled some of that spagetti, and removed major issues in logic and code which should be throwing compiler errors. I also have a debug message system which you can turn on and off which sends lots of messages showing the sequence the AI is firing in. You probably want to get on IRC if you want to really discuss what is going on.

Modifié par painofdungeoneternal, 20 mars 2011 - 09:08 .


#15
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi Pain,

Thanks for the extra information. I will have to digest that at a later time, when I have at least felt reasonaly comfortable with the basic stuff. ;) However, if your own AI system/version is more comprehensible, then maybe I should just wait to take a look at that.

By the way, I tried joining your forums, but cannot post yet. I assume I am waiting for you to authorise my joining.

"... currently in the moderation queue to be added to the forum."

Many thanks.

Lance.

Modifié par Lance Botelle, 20 mars 2011 - 09:37 .


#16
painofdungeoneternal

painofdungeoneternal
  • Members
  • 1 799 messages
Here is a very simple over view of the AI

Creatures are iterated looking for what features they have, either good benefits, or evil effects, if they are friend or foe. You cannot make a decision without all this.

The creature starts looking for things it CAN do. It uses the 2da's to determine extra data it needs, like weights. Henchspells.2da is VERY important, as is Henchclass, etc, it basically uses this as it's memory banks. If not valid it goes into failover and trys to just guess.

When it finds something it can do, it stores it into a global variable, things like best opponent, current opponent, etc are stored in these.

At some point it gives up and does some random action as determined by those global variables. Either it's found something it really likes, or it runs out of things to do and it just does something.

Note that the above is not something i could explain as it's spagetti, to do the first step involves a good 300+ functions, and a lot of shortcuts, early exits out of loops, and its adjusting variables defined 40 functions ago. It is the biggest bottleneck on performance of the game and if it's better for performance is far more important than making it so end users can modify it. The shortcuts it's using are actually not shortcuts but doing it closer to the computers true language.

I am refactoring it, with care as to not destroy how it worked originally, and my end result should do what you are trying to accomplish (ie let monsters use their basic abilities ) and hopefully be something others can follow easier. However this also requires making those abilities work a lot better too since they are very basic in how they are set up, without feats, icons, etc and other things you'd do if a player/henchman got them. It requires fixes to both the AI and the feats, spells to make this all happen.

I would suggest focusing on henchspells.2da, this is key, and the spellinfo variable you will see all over the place uses values / weights from here.

#17
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi Pain,

Having looked at the scripts and spoken to you, I believe I now fully appreciate the tangle of code that this is. Rather than duplicate the work you are already well underway in (by the sounds of it), I will simply skirt around the AI issues with "quick fixes" until your own system becomes available as a replacement. Indeed, if it does what you say (allow easier use of monster abilities), then that will be reason alone (and was my own interest and goal) to use it.

I will take a look at the henchspells.2da as you say and pick at bits here and there, but avoid the meat of it for now, simply because I now know enough that I recognise that this is a huge task to undertake and one that would interfere with any mod creation I have in mind if I even tried to take a closer look. EDIT: I took a look at that 2da file ... Unfortunately, it raises more questions, like how do I translate those numbers? Posted Image

That said, I was tryng to post on your own forums at how impressive your work and work rate appears to be. I spotted the Languages post and recognised the Readable Books section, which was good to see.

To stress though, the amount of work you are doing across the entire game is incredible and I have no idea how you manage to do as much as you do. Having joined your forum, I hope to keep up with your work and post when I have anything that might be worth mentioning.

Thanks again.

Lance.

Modifié par Lance Botelle, 20 mars 2011 - 10:43 .


#18
The Fred

The Fred
  • Members
  • 2 516 messages
Ripping out the AI and pretty much starting from scratch is probably the best option, but by no means simple or easy. I think I actually tried to build my own AI from scratch way back when in NWN1 but I never got to test it because my install got bugged or something and it wouldn't let me run my modules (I think they got corrupted or whatever). It's certainly a mammoth task, anyway. In terms of making simple tweaks to a monster's behavious, the HotU override AI system is probably the way to go. It's kind of treating the symptom rather than the cure, but the AI is kind of like the common cold in this respect. ;-)

#19
kevL

kevL
  • Members
  • 4 052 messages
this is just a 'me too', i agree post

Lance Botelle wrote...

kevL wrote...

here's a strand of pasta .. re. NW_FLAG_FAST_BUFF_ENEMY

It starts by setting the Flag in the onSpawn ( eg, nw_c2_default9 )
then in the onHeartbeat, it checks the Flag (eg. nw_c2_default1 )
if TRUE, run TalentAdvancedBuff() ( x0_i0_talent )

the function TalentAdvancedBuff() checks for & runs a whackload of ActionCastSpellAtObject() with the bInstant parameter. (Fast Buff)


It looks as though you are looking in older versions of the default scripts. For instance, the nw_c2_default1 version I am looking at has HenchTalentAdvancedBuff instead of TalentAdvancedBuff, which is found in the hench_i0_ai include and not the x0_i0_talent that you refer to. So, my first question is, can we totally ignore the older include file you refer to now and only use the newer version of the function and newer include?

I've been playing (for now) with older scripts, yes - i just grabbed one because it made the point. So no worries. But an include file is an include file and a function is a function, if ya get what I mean, L.

The funny thing I had difficulty with is the fact that most of the AI functions return Ints, insead of Voids; what I gather is happening, however, is that by doing things that way the Devs can run actions and check for conditions all at one go, allowing the AI to run yet more actions and check for more conditions, in what is presumably a logical mess. More sauce, pls

As far as I can see, this kind of function does both the spell (if they have it) and returns an int to say if they have been successful or not. If the int returned is FALSE, then the script that called the function should carry on to determine what else the creature may try to do in this round.

you're right, I was thinking about this more after my last post, and realized it's more likely that a TRUE return will end a sequence.

The strand ends, I'm guesstimating, once a check fails and returns FALSE. Notice, Lance, that AI is not simply the onCombatRoundEnd script; any event can start the pasta cooking.

See my comment above. I think returning FALSE actually means continue to look for another action, whereas returning TRUE may be what causes it to end, because it has found an action the creature can do.

sic.

I think that if there is some other call to this code is partly what is confusing me. I mean I can see another creature calling this AI check during its combat turn, but surely nothing else needs to?

uhmmm, nom nom nom!

I am considering copying all the includes into named versions of my own and including/editing those versions for the AI to see what happens. E.g. The HenchDetermineCombatRound(); function would link to the function in an attached include called alb_combat_ai or something like that. As I edit more and more sub includes, I will rename them too, to remove the junk not required.

It is tempting to do this, just hunt out what you ( or i ) know, understand, test, and trust and start building up our own libraries. It's better to wait for Pain to get his handy-dandy bitwise Tardis fully operational, though. Of course a little sortie or two before then wouldn't be remiss ..

- end transmission

#20
MasterChanger

MasterChanger
  • Members
  • 686 messages

kevL wrote...

It is tempting to do this, just hunt out what you ( or i ) know, understand, test, and trust and start building up our own libraries. It's better to wait for Pain to get his handy-dandy bitwise Tardis fully operational, though. Of course a little sortie or two before then wouldn't be remiss ..


I will say that if there's some very specific behavior that you want to create for certain creatures, rather than a whole system, it's definitely do-able by using the "X2_SPECIAL_COMBAT_AI_SCRIPT" string that DetermineCombatRound checks for. I've used this to create flanking AI for animals that hunt in packs. There's not much of a hit to performance because it only runs during combat (making it even better than just tossing it into creature HB) and it gets called at the very start of DetermineCombatRound.

If you want to change creature behavior and decision-making in more fundamental ways, then yes, wait for Pain.

#21
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi All,
I think the general agreement is to tweak via the script override system until the system Pain is working on sees the light of day. However, this does beg one final question from me ...
While I see there is the option of adding an AI script by placing it in the "X2_SPECIAL_COMBAT_AI_SCRIPT" variable that the hench_o0_ai script picks up,  what is the purpose of the AI pickup that occurs in the script that fires before the default AI script is fired itself ... i.e. When HenchDetermineCombatRound is fired inside hench_i0_ia, it can also fire an alternative AI script that is stored on the creature inside "AIScript" even before the part above.

I am guessing that the alternative pickup from the HenchDetermineCombatRound function ("AIScript") is older (?) and better not used, as there are a number of other functions that fire prior to the later AI pickup that fires within the hench_o0_ai include using "X2_SPECIAL_COMBAT_AI_SCRIPT".

However, clarification between how these two alternative AI pickups behave would be appreciated. As we know, simply reading the code does not help, but someone with experience may be able to enlighten?

Lance.

#22
The Fred

The Fred
  • Members
  • 2 516 messages
Afaik, the only things which are meant to run before an override AI script are basic sanity checks, like checking if the creature is "dying" (SoU), busy, actually in combat, etc. Otherwise, the override code should come right before pretty much anything else.

Incidentally, it sounds like you've got the hang of bitwise operators now, but if anyone hasn't I find it easier to think about them a little like "tens and units" adding (admittedly, it's "twos and units"):

10010 &
10100
---------
10000

10010 |
10100
---------
10110

It's a lot easier to see what's going on that way.

#23
Guest_Chaos Wielder_*

Guest_Chaos Wielder_*
  • Guests
"X2_SPECIAL_COMBAT_AI_SCRIPT" pretty much runs before anything. Like Fred says, it's going to have sanity checks--do I even exist?!--and things like that. I think you should look in this direction, as it does allow for a great deal of control without requiring real programming expertise(once bitwise operators come out, it can only get more painful). :P

#24
Quilistan

Quilistan
  • Members
  • 111 messages
I have also been noticing that certain special abilities are being picked up and used and others are not. Is there a list somewhere of the supported abilities?

Example: acid breath wasn't being picked up, but acid bolt does (this is tested on the same creature)

If there is a list it would at the very least help.

#25
M. Rieder

M. Rieder
  • Members
  • 2 530 messages
Now I'm trying to bitwise everyone's game ownership as stated in their signatures. Thanks for breaking my brain!

In all seriousness, though, thank you all for that discussion of bitwise operators. Its really neat. I am assuming that they are used primarily to streamline code and make it run faster, correct?

One other question, is "X2_SPECIAL_COMBAT_AI_SCRIPT" the name of a local string stored on a particular creature, with the value of the string being the name of the special script that you want it to run in combat.

Modifié par M. Rieder, 24 mars 2011 - 05:17 .