Infinity Engine Scripting Discussion
Belanos
Member Posts: 968
I PM'ed horredtheplague to get some answers to things I didn't quite understand while I've been working on my own AI scripts. He mentioned that he'd prefer to have a forum discussion about these sorts of things so other people could benefit. So I'm starting this thread and pasting his reply to a question I asked regarding the trigger condition called HasBounceEffects. This is what he mentioned about that query:
No, there aren't a lot of guides out there for IE scripting. But IEDSP has the best information, even if it's not 100% complete or accurate. From there is a good definition of what HasBounceEffects checks, even if it's a bit cryptic.
0x40B0 HasBounceEffects(O:Object*)
Returns true if any of the specified object is currently affected by any of the listed effects:
0xc5 Bounce Projectile
0xc6 Bounce Opcode
0xc7 Bounce Spell Level
0xc8
0xca Bounce Spell School
0xcb Bounce Secondary Type
0xcf Bounce Specified School
0xe3 Bounce School Decrement
0xe4 Bounce Secondary Type Decrement
Now, in order to see if a spell qualifies, you have to check its effects for one or more of the above. You can see it's a pretty broad range, and definitely not the most precise way to detect a certain spell's effects. Covers both spell turning and prot/missiles, e.g. It's most widely used as a blanket check for spell turning/trap/deflection (to see if your casting is wasted, or even worse).
That's why we invented the Detectable Stats (spells) system. If I was told right, the Neera add-on for BG2EE has it installed by default. But that is definitely the way to go, until Beamdog comes out with their planned ability to detect opcode values on anyone. As far as a description of DS, if it doesn't have one included I may have a crude copy I made from DSv1.0 (first public release) laying around on my hard drive at home.
No, there aren't a lot of guides out there for IE scripting. But IEDSP has the best information, even if it's not 100% complete or accurate. From there is a good definition of what HasBounceEffects checks, even if it's a bit cryptic.
0x40B0 HasBounceEffects(O:Object*)
Returns true if any of the specified object is currently affected by any of the listed effects:
0xc5 Bounce Projectile
0xc6 Bounce Opcode
0xc7 Bounce Spell Level
0xc8
0xca Bounce Spell School
0xcb Bounce Secondary Type
0xcf Bounce Specified School
0xe3 Bounce School Decrement
0xe4 Bounce Secondary Type Decrement
Now, in order to see if a spell qualifies, you have to check its effects for one or more of the above. You can see it's a pretty broad range, and definitely not the most precise way to detect a certain spell's effects. Covers both spell turning and prot/missiles, e.g. It's most widely used as a blanket check for spell turning/trap/deflection (to see if your casting is wasted, or even worse).
That's why we invented the Detectable Stats (spells) system. If I was told right, the Neera add-on for BG2EE has it installed by default. But that is definitely the way to go, until Beamdog comes out with their planned ability to detect opcode values on anyone. As far as a description of DS, if it doesn't have one included I may have a crude copy I made from DSv1.0 (first public release) laying around on my hard drive at home.
Post edited by Belanos on
1
Comments
Another thing I've been wondering about, I see that both you and Cierrek(sp?) in his eSeries scripts uses an interrupt timer for pretty much everything that's in the scripts. What exactly is the value of that? In the eSeries scripts at least the very last action listed has a character attacking for one round, so wouldn't that make having a timer redundant? Nothing will get done until that action is completed after all. The spell timer I can see since not all spells require a full round to cast, but the interrupt timer I don't quite get.
Also, does the STATE_HELPLESS condition also include things like being Held or being caught in a Web, both of which have Stat checks for them. Wouldn't having both conditions listed be kind of redundant?
The reason this was not changed, is because doing so would have broken a lot of BG2 mods. Beamdog does their best in this department, believe it or not. It has plenty of former modders on staff, and they haven't forgotten their roots (yet).
STATE_HELPLESS: I wonder the same. Everything that could render somebody "helpless" is essentially covered. Maybe somebody else knows. I only use it in a blanket command, just out of paranoia that I'll miss something important
Do you understand how STATE.IDS works--the concept of a bitwise? If not, I'll try to explain this one. Bottom line, you can make several state checks, like an OR statement, from a single line of code. That's how those old community-standard custom entries were made (STATE_HARMLESS, STATE_OUT_OF_ACTION, etc).
Only thing I've noticed is that weidu's compiler chokes on the >9 values (A-F). At least, when entered as raw hex code. Sometimes I have to write these as 2 lines of code instead of 1.
INTERUPT timer: If you check the most recent versions of BP Series, you'll see it's no longer a timer but a 0-1 toggle (more on that in a bit). The last action in a BPSeries script is not AttackOneRound(), but Attack(). The latter goes on until the opponent is dead or it's broken by another action. Now, note that all the spells work on a basis of ActionListEmpty(). This is done to prevent your action queue from getting overstacked, clogged, and causing lag and incorrect execution. Now, what happens if you have a constant action going, and what can break it requires the action list to be empty? Nothing, it keeps playing til its bitter end. This was why mages e.g. got locked into dart combat instead of spellcasting.
I saw this same potential with using a timer, so I switched to a toggle. Why w/ the timer? It was set for 1 second, so the script could interrupt right away if needed. Sounds great. But what happens if your "cast n' attack" timer is still counting to 5, when that 1 second timer expires? No spells can get cast, nothing gets interrupted--that's what. And our mage goes on to the dart league finals. But by using a toggle, it's not bound by time limits and the attack can be broken during any parsing round (parses 1/second). Tested, works much much better.
One other thing I've noticed is that I see some very weird Race entries in both your scripts and those of the eSeries. In yours it comes out as Morningstar, and in the eSeries as Wyvern. I thought maybe it was the same sort of thing as the MAGE_ALL, LONG_BOW conflict but I don't see anything in the ids file that would cause that, and I can't figure out what that race is supposed to be. In the eSeries it's always found in conditions that have something to do with magic, usually along with the MAGE_ALL etc. conditions.
As for the STATE_HELPLESS condition, I've taken to using the STATE_OUT_OF_ACTION one. Reading about it on the IESDP site, it seems to cover just about everything I'd want for targeting parameters, at least if STATE_HELPLESS works the way I think it does. Just in case though, I've added the stat checks for HELD etc. I'm just wondering if that's overkill.
I'll keep your idea for the interrupt toggle in mind. I still don't fully understand the value of that though, at least if I'm using the AttackOneRound command. I can see that a simple Attack command would be more flexible though, since an enemy might be dead before the round is over.
IF
OR(2)
ActionListEmpty()
Global("INTERUPT","LOCALS",0)
!InParty(LastSeenBy(Myself))
THEN
RESPONSE #100
SetGlobal("INTERUPT","LOCALS",1)
Attack(LastSeenBy(Myself))
END
IF
StateCheck(LastSeenBy(Myself)),STATE_REALLY_DEAD)
THEN
RESPONSE #100
SetGlobal("INTERUPT","LOCALS",0)
END
Looking at your current script, I get the sense that it would be possible for a character to stop an attack before he/she was finished and switch to something else. I think with this method, he/she would continue attacking an enemy until it was finished off before doing something else. Keep in mind that I'm using Interupt in the opposite way that you are in your script, my 0 is like your 1. You got me thinking about using a toggle as well, after noticing that one of my party members had a slight pause after killing his enemy. I'm guessing that the round hadn't finished yet, so he just stood there doing nothing for a couple of seconds. It kind of sucks that I can't retain any of the formatting, this would be easier to read if I could.
Cirrerek's measure for some reason involved changing morningstar to wyvern. No idea why. The proper entry of "WYVERN" was already in the RACE and CLASS file. BPSeries instead changes the creatures to have the proper identifiers.
Also, I use unique identifiers in a very weird way in order to make sure my "cloud detector" is clearly identified. That's what the MORNINGSTAR.FAIRY_NYMPH.SHOU_FLAYER check is for. Those are identifers set on the BPINVISO creature, which is placed at a cloud spell's epicenter to give players (and enemies, if you use BP) something solid to RunAwayFrom(). And, later in the scripts (both mods) you'll find checks in the targeting blocks to make sure these cloud creatures are not targetted for combat.
Finally, if you look in my BAF files rather than the compiled scripts, you'll see I use raw numbers for IDS in many cases. While this goes against conventional wisdom and negates some of the handiness of IDS entries, it also insures my scripts can compile regardless of what the current entries say. The DS package does stuff behind the scenes that will make this approach work fine. I do this because both BP and BP Series are an integral part of the BWP install (BP is actually the grandfather of BWP, if you know its history). Some mods set these values differently than the DS package. Two that come to mind are Ding0's quest mod and Kelsey NPC. And that's just one of many IDS files, and 2 of 100's of mods. You mentioned ESeries and BPSeries having different values, same line...
P.S:
CheckStat(Myself,0,HELD)
CheckStat(Myself,0,ENTANGLE)
CheckStat(Myself,0,WEB)
CheckStat(Myself,0,GREASE)
NotStateCheck(Myself,STATE_OUT_OF_ACTION)
All check for similar yet uniquely different things. Use what's needed, that's not overkill. Sometimes you don't always need all of STATE_OUT_OF_ACTION however. Remember you can make your own customs w/ or w/o IDS entries (raw hex values will do, just don't use A-F in weidu compiler unless we can convince @Wisp to fix this).
Side Note: In some mods, STATE_OUT_OF_ACTION = STATE_DISABLED. So much for "old community standards". See why I favor those raw number entries for custom values?
ATTACK, etc:
AttackOneRound() was not used for the very reason you described. It can add blank spots if the enemy is killed prematurely. It was reported years back that it could actually make a script pause, but I've never witnessed this just the former.
AttackReevaluate() can also add pauses, especially when used for a time shorter than it takes the script runner to launch an attack. Someone that launches 1 attack/round takes 90 beats between attacks, somebody with 3/2 attacks takes 60, 2 attacks = 45, etc. As this is an arbitrary value unless you tailor a script for just one entity--the potential for a script-stutter is always there.
In your script, I'd add a Global("INTERUPT","LOCALS",1) check to the top of your second block. Otherwise, because of a currently existing bug in BG2EE, your dead foe will always ring true for See(NearestEnemyOf(Myself)). This in turn would cause that block to be an endless loop while that dead person remained your nearest enemy, unless you add this value = 1 check. Besides, if the value isn't already at 1, why does it need to be set to zero? In BPSeries, I throw a HPLT(LastSeenBy(Myself),1) into my conditions, to help remedy this problem, rather than a StateCheck. All of the various forms of death also set the victim's hit pt value to zero or lower.
If you merely want to attack a foe until they're dead, without pause, you don't need all this glitter. A simple Attack("what's-his-face") should handle that for you. It was because we didn't find this desirable (Cirrerek and I) that we set up the interrupting system. We wanted the benefits of both approaches available. You'll find that the targeting blocks will often keep finding, and thus beating on, the same foe round-by-round. If they switch, there's usually some good (defined/controlled) reason. But this system allows us to do something special once/round (cast spell, use item, etc), while still using the remaining seconds (to attack w/ a weapon).
This system works for enemies equally as well. e.g, One of the things BP does is give enemy mages a staff and a stack of darts, plus proficiencies to use them w/o penalty. That was done both so they could benefit from the cast/attack system, and because the majority of vanilla mages had no weapons/proficiencies at all. We know most mages are the arrogant sort when it comes to physical combat, but this was taking it to an extreme.
My STATE_DISEASED_FIX patch (STAT #134) however still is needed, in all 3 games. STATE_DISEASED in State.ids is, and has always been, broken.
The BPINVISO/ MORNINGSTAR bit is used for cloudkill, web, entangle, etc--any lingering AoE spell. A central point to run away from, or focus some other action on. Not foolproof, but better than just standing there.
All the State.ids customs in BP are either unique, or correspond to what you see in IEDSP
I'm pretty sure you can use numbers in base 10.
So one thing I've been thinking about recently. Looking around I discovered that Beamdog has added a script command that allows someone to add an item into a store. Coupled with a random number trigger, that has some interesting possibilities. I've been adding my own bags to the game, mainly through various stores, and it would be rather cool to have them appear randomly, rather than having me know where they all are ahead of time. But so far my IE scripting experience has been strictly dealing with AI routines. Any suggestion on how I could do something like that? I'm not sure how to make a general script trigger when a game starts up.
For sometthing like your 'bag mission', you can approach it several ways. One could be adding blocks to Baldur.bcs, the 'always running' script. Another could be blocks to AR0602.bcs, the starter script. Or, AR0700.bcs, the first script ran when you leave the dungeon (and are thus able to shop).Another way still is to paint a trigger on the floor where the player "has to" walk. Run the script from that trigger, as if it were a trap. It doesn't "have to" be done right at the start, being my point. I recommend either AR0700 or the floor trigger, simply because AR0602 and Baldur scripts are pretty clogged up already (much worse when several/certain mods are added).
So I'd like to offer you a tip in exchange. I've noticed that you sometimes use AttackedBy([ANYONE],Default), or !AttackedBy([ANYONE],Default), as a condition. That doesn't necessarily make any sense. In a battle I had with a pack of Gibberlings, I noticed while reading the dialogue box text that one of them was attacking Dynaheir. But it was a long way off and had to travel to her in order to actually do any damage. But my party destroyed all of the Gibberlings before they even got close to her, so she was never in any real danger. After that I changed my conditions to:
OR(2)
!AttackedBy([ANYONE],Ranged)
!Range(LastAttackerOf(Myself),4)
OR(2)
AttackedBy([ANYONE],Ranged)
Range(LastAttackerOf(Myself),4)
Depending on what type of spell it was, offensive or defensive. It's just a little more specific IMO. I've noticed that you sometimes use HitBy([ANYTHING],Missile). That's even better.
Unfortunately both attempts are in vain (yours and mine).
HitBy([ANYTHING],MISSILE) will actually not help. The attack style (ASTYLES) MISSILE or MELEE identifiers don't actually work. Unfortunately. So, the MISSILE check defaults to DEFAULT (0). Unless BG2EE fixed it, and I missed it. Same story with DAMAGES.ids unfortunately, unless recently fixed. Only CRUSHING (default = 0) works, not HitBy(xxx,COLD) HitBy(xxx,SLASHING) etc.
So, even in my scripts, this was wishful thinking. The inconsistency wasn't from lack of realizing there were IDS entries made for the task, it was from knowing the futility of it. At one point I was hopeful it had been fixed, but it wasn't. That was some time ago (a year/so ago). If you want to know for sure if it works now, you can set up a test environment by giving a couple creatures a script that displays string heads when a certain attack style or damage is done: "I was hit by Missiles", "I was hit by Cold", and so on. If you find it is working, let me know. I'd gladly refine some scripts if I had this capability.
Also, the other flaw here is in the object choice. [ANYONE] or [0]. The use of raw IDS entries looks for the very nearest creature that qualifies for this condition. [EVILCUTOFF.0.0.CLERIC_ALL] finds the nearest enemy clerical type w/in sight range. You can use (Second,Third,etc)NearestEnemyOfType([w.x.y.z]) format in conjunction with these, and it will work. So, if you really want to get a target in the far range--look at the BPMONK "near/far" system.
You'd actually need a series of blocks more like this, to detect a far-off archer and target them:
HitBy(NearestEnemyOf(Myself),DEFAULT)
!Range(NearestEnemyOf(Myself),4)
See(NearestEnemyOf(Myself))
HitBy(SecondNearestEnemyOf(Myself),DEFAULT)
!Range(SecondNearestEnemyOf(Myself),4)
See(SecondNearestEnemyOf(Myself))
[You can change EnemyOf(Myself) to EnemyOfType([x.x.x.x.x]) if you prefer]
Even HitBy and AttackedBy can let you down in a firefight--I try to only use them in targeting blocks when I am looking for a general statement. The object entries are more reliable (LastAttackerOf, LastHitter).
Finally, in your example blocks, I don't think you want the two negative statements in an OR() statement. I think those should be AND instead:
!AttackedBy([ANYONE],Ranged)
!Range(LastAttackerOf(Myself),4)
But that depends on what exactly you're using them for. If as two sides of the same coin, my statement stands.
So I've been working on my idea of the random bags, and I've hit a few roadblocks. First of all, I discovered how to create a container script that creates the item when it's opened, with a random number to determine it's probability. I was also thinking of adding one to various enemies in the game, so that I would have to earn some of those bags in battle. But I'm not sure if I can get it work. If I create an override script that attaches to the character can I get it to point to my party as a condition somehow? The only thing I've been able to think of using is !PartyHasItem, along with the RandonNum condition. So it would be something like:
If
!PartyHasItem("Bag"01)
RandomNumberLT(100,51)
THEN
RESPONSE #100
CreateItem("Bag01")
Now that works with containers because I can use the "Opened By" trigger, but I'm not sure how I can accomplish it in a combat situation, so that a Bag appears in an enemy's inventory. Any suggestions? I was thinking of a kind of OnDeath script using the State_Really_Dead check, but I'm not sure if that would work.
It's also possible the engine doesn't quite support it yet even though it's listed as an action. We added that action for... future projects. Sharing WEDs is not a problem. Containers are stored in the ARE file itself, so the containers won't cross over between identical tilesets. The only possible conflict would be if the areas shared an area script, which isn't the case anywhere in the game.
The problem lies in that 2% that generally encompasses high-end AI scripting. I don't think people really appreciate how hard it is to do good, high-level AI--there's a reason why most 'tougher X' mods tend to beef up enemy stats in lieu of more intelligent behavior. The engine's not really built for it, so good AI scripts include a pile of tricks that become really hard to follow for someone who hasn't run into the same issues themselves. Folks live Cuv, horred, and aVENGER have been plugging at it for years now. Hell, DavidW wrote his own tool in Python to get it to work sensibly for SCS.
You're also looking at it from the perspective of an experienced modder. With the release of the EE games, there will no doubt be a whole new generation of people who are approaching modding the games for the very first time. It would no doubt be helpful to them to have some place to go with their questions. I've spent time at The Gibberlings Three and at Pocketplane recently, and they're practically "ghost towns" these days. There's a lot more traffic here now than at both of those sites combined, so they're losing their relevance as far as IE modding is concerned. They're great places to go for getting mods, but they're no longer all that useful as far as learning how to do it is concerned. You could ask a question there and not have it answered for months, if at all.
I may as well mention this one here, since the only other place I mentioned my discoveries was in the BP Series changelog and this seems like the righ place.
This is a hidden function that exists in BG2 even, unearthed by AvengerTeamBG:
ACTION.IDS
34 UseItemExt(S:Item,O:Object,I:Slot*SLOTS,I:Abil)
This action allows you to use items that have > 1 ability, that also aren't weapons. I mentioned this in a (now-buried) thread around day 1 of BG2EE release, but since then I've learned a couple of things about it.
1) It has to go above the existing 34 UseItemSlot entry, or it doesn't work. This makes UseItemSlot not work, but there were only a mere handful of vanilla scripts using it (barely, not even at its potential --same items same slot every time). I wrote a weidu patch to change them over (in latest BP Series) as well as ACTION.ids.
It's worth the effort, as this is a much handier function than UseItemSlot by a longshot. It combines UseItem, UseItemSlot, and UseWeaponAbility into one sweet command.
2) This has to be the first action line after RESPONSE #100, or it doesn't work.
3) Confirmed: this does work for multiple item abilities of the same item, if you follow the blueprint above. I:Abil entry--start with 0 for ability 0, 1 for 1, etc. Just like with UseWeaponAbility
4) Using the proper SLOTS.ids entry (SLOT_HELMET for a helm, etc) seemed helpful to me in my testing, but the default 0 SLOT_AMULET is also supposed to work according to AvengerTeamBG.
My next big push on BPSeries, when I find the time, will be adding code to handle item usage for all the vanilla items in all 3 games. There's already plenty of working examples in the scripts to see it in action (made it to "H" in BG2EE, thus far) so check it out if you like.
Containers and their items are stored in ARE files so predefined. However there is some randomness with the RNDTRE01-05 items that create a random item based on 2DA tables, when that is decided I don't know, I guess 1st time the area is loaded.