ToEE's AI

Discussion in 'Tech Guides and Help Threads' started by Sitra Achara, Aug 9, 2008.

Remove all ads!
  1. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,613
    Likes Received:
    537
    No way! I've tested it many times on my computer, and it worked fine.

    Can you post the script that breaks the san_start_combat function?
     
  2. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    Sure, from memory it was basically:

    Code:
    def san_start_combat( attachee, triggerer ):
    	leader = game.leader
    [COLOR="Red"]	if ( (attachee.map == 5093) and (game.global_flags[954] == 1) ):
    		obj.obj_set_int(obj_f_critter_strategy, 20)[/COLOR]
    	elif ( (game.quests[67].state == qs_accepted) and (game.global_flags[963] == 0) ):
    		game.counters[0] = game.counters[0] + 1
    		if (game.counters[0] >= 2):
    			for pc in game.party:
    				if pc.type == obj_t_pc:
    					attachee.ai_shitlist_remove( pc )
    			game.global_flags[963] = 1
    			leader.begin_dialog( attachee, 1 )
    			return SKIP_DEFAULT
    	return RUN_DEFAULT
    with red being the relevant bit.

    IIRC, it actually broke the entire file (or I assume it did), because the following san_first_heartbeat script from the same file stopped working after the strategy thing was added:

    Code:
    def san_first_heartbeat( attachee, triggerer ):
    	if (attachee.map == 5093 and game.global_vars[960] == 3):
    		attachee.object_flag_unset(OF_OFF)
    	elif ( (game.party[0].reputation_has(35) == 1) and (attachee.map == 5121) ):
    		attachee.object_flag_unset(OF_OFF)
    	elif ( (game.party[0].reputation_has(35) == 0) and (attachee.map == 5121) ):
    		attachee.object_flag_set(OF_OFF)
    	return RUN_DEFAULT
    [edit]

    'from toee import *' and 'from utilities import *' are invoked at the start of the file.
     
  3. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,613
    Likes Received:
    537
    That's probably because the object handle "obj" is undefined and points to nothing :)

    Try attachee.obj_set_int(...) instead.
     
  4. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    Yay, it works. :) Only now, the mobs with changed AI seem to go to *no* AI (they return to their original coordinates and just stand there) after they kill a target, for reasons I can't begin to fathom. :(

    Thanks though, at least it's half-way working.

    [edit]

    Seemed to fix it with a 'return RUN_DEFAULT' after the AI change script.
     
    Last edited: Aug 27, 2010
  5. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    @SA, have you ever hit the wall with the number of AI commands possible per strategy.tab entry? For some reason a wizard entry I was doing arbitrarily stopped working after 15 different (and unique) spells, but I'm not sure if that was 'the wall' or if I just stuffed something up or if ToEE just got pissy about it at random.
     
  6. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,613
    Likes Received:
    537
    Not that I noticed...
     
  7. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,613
    Likes Received:
    537
    Looking around the files, there appears to be a file called "rules\spell_priority.mes". Perhaps it's a kind of default cast-list for the AI. (it hasn't been modified, so it's not in the Co8 files)
     
  8. PKSmith

    PKSmith Member

    Joined:
    Nov 30, 2011
    Messages:
    3
    Likes Received:
    0
    Confirmed... ' is what you lead a cell entry in Excel with if you want it to interpret it as a text entry (which allows you to enter numbers as text rather than as numbers)... it's a feature for spreadsheet users! ;)
     
  9. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    Here is a nominally complex AI/start combat script for Deggum that I have been working on. It's not some major insightful thing, but it's more complex than I've attempted before (and it actually works, lol). It operates on the basic principal of there being three directions (at least - this would seem to me to be the bare minimum) that a reasonably intelligent spellcasting NPC should be able to go: normal casting, healing, and melee. IMO, currently very few NPCs do all these things well together.

    *Thanks to Sitra and Livonya for most of the nuts and bolts behind all this.* I'm mainly just posting this here as an example of an 'intelligent' AI that works for anyone interested down the line.

    First of all, spellcasters imo don't do the above three things well together because:

    1. They should use five foot step to move out of AoO range when casting spells or drinking potions. You can add this AI to the start of their strategies, and they will then use five foot steps before casting/drinking, but then they will also use them with melee, bizarrely. This manifests in them wasting every second turn executing a five foot step (when they are carrying out their melee attack AI instructions, which is usually after all spells have been exhausted): they do that and nothing more one turn, then attack the next, then rinse and repeat.
    2. Because of 1, you can't really include five foot step in an NPC's strategy who is going to be a caster and a melee-er. Without that though, they are especially vulnerable to dying when low on HP and casting healing spells and/or drinking potions, because they will just happily stand there and do that right in front of the guy/gal who is about to kill them for it.

    So what do we do? Well, we give them different strategies to employ under different conditions using SA's attachee.obj_set_int(obj_f_critter_strategy, xxx) script. For our purposes here, I'm assuming Deggum limits his aspirations to his normal casting repertoire, melee attacks, and healing himself. (I'm also assuming that, being an evil asshole, he is not really concerned with healing his bodyguard bugbears, but that could be scripted as well.) So Deggum has to know when to do what though, so we keep track of his AI changes and spells cast with variables that are incremented at the appropriate time in his san_start_combat script (the script that runs at the start of every NPCs turn in each combat round). The variables we will use are 735 (to count his healing buffs) and 736 (to count his normal casts). The AI's we will use are 'Deggum Casting (225)' (this is his original AI entry renamed), 'Deggum Healing (443),' and 'Deggum Melee (444).' As we know, for an AI to be changed via script, it has to be referred to by line number, so we name the scripts with the line number so we know which they are. The latter two are new AI entries appended to the end of strategy.tab. Furthermore, Deggum has 2 'Cure Light Wounds' spells memorized and has 2 'Cure Moderate Wounds' potions in his inventory, so he is capable of taking 4 healing actions on his own behalf. He has 10 other normal spells memorized, so we will count those up to 10 as well so we know when to tell him to abandon that strategy. (Being a cleric 4/wizard 3, he actually has 6 other cantrips/whatevers memorized, but these are not worth casting. He casts two buffs on his heartbeat prior to combat.)

    Here's the script:

    Code:
    def san_start_combat( attachee, triggerer ):
    	if (attachee != OBJ_HANDLE_NULL and critter_is_unconscious(attachee) != 1 and not attachee.d20_query(Q_Prone)):
    		if (obj_percent_hp(attachee) <= 50):
    			if (game.global_vars[735] <= 3):
    				attachee.obj_set_int(obj_f_critter_strategy, 443)
    				game.global_vars[735] = game.global_vars[735] + 1
    			elif (game.global_vars[736] <= 9):
    				attachee.obj_set_int(obj_f_critter_strategy, 225)
    				game.global_vars[736] = game.global_vars[736] + 1
    			else:
    				attachee.obj_set_int(obj_f_critter_strategy, 444)
    		else:
    			if (game.global_vars[736] <= 9):
    				attachee.obj_set_int(obj_f_critter_strategy, 225)
    				game.global_vars[736] = game.global_vars[736] + 1
    			else:
    				attachee.obj_set_int(obj_f_critter_strategy, 444)
    	return RUN_DEFAULT
    I'll go through it line by line.

    Code:
    	if (attachee != OBJ_HANDLE_NULL and critter_is_unconscious(attachee) != 1 and not attachee.d20_query(Q_Prone)):
    If Deggum is not dead, unconscious, or prone, do the following:

    Code:
    		if (obj_percent_hp(attachee) <= 50):
    If Deggum's health is under 50%, do the following:

    Code:
    			if (game.global_vars[735] <= 3):
    If Deggum has cast 3 or fewer healing spells and/or drank 3 or fewer healing potions, do the following:

    Code:
    				attachee.obj_set_int(obj_f_critter_strategy, 443)
    				game.global_vars[735] = game.global_vars[735] + 1
    Change Deggum's AI to 'Deggum Healing (443)' and increment the healing variable.

    Code:
    			elif (game.global_vars[736] <= 9):
    If instead Deggum has already executed 4 healing measures, and if he hasn't exhausted all his normal spells, do the following:

    Code:
    				attachee.obj_set_int(obj_f_critter_strategy, 225)
    				game.global_vars[736] = game.global_vars[736] + 1
    Go back to his normal 'Deggum casting (225)' strategy and increment the normal casting variable.

    Code:
    			else:
    If Deggum has exhausted all his healing and normal spells, do the following:

    Code:
    				attachee.obj_set_int(obj_f_critter_strategy, 444)
    Switch Deggum to his 'Deggum Melee (444)' strategy.

    At this point we have scripted it so that Deggum will take 4 healing-related actions (spells or potions) on his own behalf if his HP are under 50%, and if he has used those all up, he will go back to casting spells. If he has used all those up, he will go to melee.

    Code:
    		else:
    If Deggum is not under 50% HP, do the following:

    Code:
    			if (game.global_vars[736] <= 9):
    If Deggum hasn't exhausted al his normal spells, do the following:

    Code:
    				attachee.obj_set_int(obj_f_critter_strategy, 225)
    				game.global_vars[736] = game.global_vars[736] + 1
    Switch Deggum to his 'Deggum Casting (225)' strategy so he casts his normal spells and increment the normal spell casting variable.

    Code:
    			else:
    If Deggum has used up all his normal spells, do the following:

    Code:
    				attachee.obj_set_int(obj_f_critter_strategy, 444)
    Switch Deggum to his 'Deggum Melee (444)' strategy so he can fight.

    Code:
    	return RUN_DEFAULT
    The normal run default script thingie.

    So at this point we have scripted it so that as long as his HP are above 50%, Deggum will cast his normal spells provided he has any, and he will shift to melee once his normal spells are exhausted.

    Note that according to the script, Deggum will shift to healing measures at any time his HP are lowered below 50%, so that could happen anywhere or nowhere along the way. But he won't mix melee and normal casting (with the exception of things like AoOs). We don't really want him to, as he's supposed to be a powerful spellcaster. Also remember that when casting normal and healing spells, Deggum will take five foot steps to protect himself if necessary. When in melee, he won't.

    I've given Deggum a fairly intelligent (I hope) normal spell progression in his AI strategy as well. It is:

    1. five foot step
    2. cast Mirror Image on himself
    3. cast Bless on himself
    4. cast Spiritual Weapon on a nearby opponent
    5. cast Hold Person on an opponent with low will
    6. cast Magic Missile x3 on a damaged opponent
    7. cast Enlarge Person on himself
    8. cast Bull's Strength on himself
    9. cast Inflict Moderate Wounds on a nearby oppponent
    10. attack closest (this is a failsafe so he doesn't just stand there if something goes wrong, but ideally he should never melee attack using this AI.)

    In this fashion he has no reason to run around in battle exposing himself to AoOs until he gets ready to fight with Enlarge Person and Bull's Strength after his other spells are used up. Being enlarged, he can then cast IMW on a nearby opponent without actually entering their reach area, which is a bonus for him. Then he can melee accordingly as well.

    Deggum's healing strategy is fairly simple:

    • five foot step
    • use potion (his 2 CMW potions in inventory)
    • cast Cure Light Wounds on himself x2
    • attack closest (failsafe, see above)

    His melee strategy is supremely simple since his dexterity is atrocious and he has no business running around the battlefield provoking AoOs:

    • attack closest

    ~

    Possible drawbacks I can think of with all this are that if somehow the variables get incremented improperly, Deggum may discontinue casting spells while still having some available. No doubt there are others I haven't thought of as well.

    We should really add some contingencies like this to all the healing potion chugging bugbears out there who die needlessly due to AoOs. :eek:_O:
     
  10. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,029
    Likes Received:
    42
    I've observed over a fairly long period of time now that 'target friend hurt' is unreliable. NPC healers who use that AI won't always do anything about injured allies. Might have something to do with distance, but a lot of the time they simply ignore injured friends. When it works however, it seems to 'clump;' they go around healing like mad. Or not at all. Or they may start doing it reliably once they get jump started, but often it's tough to get them going.

    There are no real alternatives here, so the NPCs I've modded to use this AI will be sticking with it. I just wish it worked better.
     
  11. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,613
    Likes Received:
    537
    Don't know if this has been mentioned, but the last line in strategy.tab has to be a blank line. I have discovered this after many many crashes and attempts to solve said crashes.
     
  12. marc1967

    marc1967 Established Member

    Joined:
    Jan 19, 2014
    Messages:
    578
    Likes Received:
    60
    I've been delving deeply into making smart scripts with the obj_f_critter_strategy method, and using smaller strategy entries. It's been both satisfying and successful. Thanks Sitra Achara for starting this thread and all the others who contributed to it.

    One of the things I wanted to accomplish, and have, was stopping wasted spells from firing for out-of-range targets. This happens a lot especially with short range spells from low-level spellcasters when combat gets scattered. For example, a 2nd level sorcerer casting a Ray of Enfeeblement with "target closest" (a range of 30 feet in this particular case) will still fire the spell if the target is out of range. So he starts the spell and looks all cool, then the spell fizzles because the target is 34 feet away. And then he looks around all stupid and embarrassed.

    Well I came to a satisfying resolution for this. I just computed the closest non-unconscious/non-sleeping target, and if it was more than 30', he would attack the closest guy instead of wasting the spell. This is acceptable to me so far.

    For the next evolution though, I would like to expand the action so that if he is out of range but still within 60', he will move close enough with his move action (30'), and then use the remaining time to cast the spell with a 30' range. I can do the math on this in the script, but is the strategy.tab sophisticated enough to pull this off? I think this would also be useful for archers chasing down enemies instead of standing and not firing when out of range.

    "approach" uses the full turn of movement, and also gets as close as possible. I'm looking to move to a certain distance with the move action, and then cast the spell with the standard action, but I don't think I've seen then done by an npc. If anyone has seen this, or has any ideas let me know.
     
  13. sirchet

    sirchet Force for Goodness Moderator Supporter

    Joined:
    Dec 6, 2003
    Messages:
    3,721
    Likes Received:
    49
    I've seen NPC's do a five foot step, (which leaves you a full action) but if five feet would work to get close enough to cast I don't know and can you make him take the five foot step towards the target instead of away from the nearest foe is again beyond me.
     
  14. Shiningted

    Shiningted I want my goat back Administrator

    Joined:
    Oct 23, 2004
    Messages:
    12,654
    Likes Received:
    352
    Maybe partial charge, Marc? I don't know if it does anything but it is in there.
     
  15. marc1967

    marc1967 Established Member

    Joined:
    Jan 19, 2014
    Messages:
    578
    Likes Received:
    60
    I'm definitely looking for more than 5 feet, plus I think that only kicks in and steps away if he is threatened. The mysterious "partial charge" was actually where I was going next in desperation. It's the last thing I haven't explored yet, and it's name almost makes it seem like there is something there to work with. But moving more than 5 feet and casting a spell on the same turn doesn't look likely.

    While I'm posting, here's some unusualness I've run across related to the AI and strategy.

    - If you use 'cast single' instead of 'cast area' on a spell with a cone like 'Color Spray' or 'Cone of Cold', it will only affect the targeted creature and ignore everyone else in the cone.

    - If you use 'clear target' (which erases the currently selected target, and also the defaults) and then the various target commands that come next don't find a valid target, the expected result is that the npc will stand there not attacking. However, he will first chase around the pc at game.party[0] until he kills him. But after game.party[0] dude is dead, he will then stop attacking as expected and ignore everyone else.

    - These are the only enemy target commands that will always find a target:

    target closest
    target damaged
    - will move on to undamaged targets if they are all that is left.
    target high ac
    target low ac
    - this is why that nasty raging brigand leader in the moathouse rushes through everyone else and wastes your wizard in the back row, hehe.

    - These may or may not find a target depending on if there is a target that meets the criteria:

    target threatened - Targets only threatened areas (closest first).
    target range - Targets only pc's with ranged weapons (closest first).
    target prone - Targets only pc's that are prone (closest first).
    target bad fort - Targets closest pc with a class that uses the 'poor' base save table for fortitude, regardless of actual value. Will not target 'good' base save pc's at all.
    target bad will - ditto
    target bad reflex - ditto

    (if "clear target" is Not used before these six target commands, and it fails to find a target, the AI will pick a default target each turn anyways based on previous target selection or initiative and attack. But this may make it look like the target is being chosen by a certain targeting command, when in fact it's the default that's doing it.)

    I am pretty sure about these two groups above, as I have tested it a lot, but I don't want to say absolutely proved (covering my ass legally there). But what the above means is that in strategies.tab where there are long strings of target commands like this:

    Code:
    target ranged		target closest		target threatened		target damaged		target low ac		target prone
    None of the first four target commands before 'target low ac' are ever used since 'target low ac' will always find a target and overwrite them. 'target prone' will then overwrite 'target low ac' if someone is prone.

    - Sorcerers are very difficult to use with strategy.tab, since they will run through all their spell slots for that level with the first spell listed in the strategy. Two rare ones in the game are Darley who actually doesn't have an AI (she stabs with her dagger when attacked :(, although it looks like someone tried to write one since there is an entry called Darley in strategies.tab), and Mona who runs through magic missiles if attacked. The evil sorceress lady in the slave traders group actually uses wizard spells probably for this reason.

    - Spells that have a range of "personal", such as True Strike and Shield, will not be cast a second time while active even if the strategy calls for it (this is a good thing). But it will recast spells that can target others redundantly like Mage Armor and Bull's Strength, even if you are casting them on yourself.
     
    Last edited: Sep 2, 2014
Our Host!