Auto Combat AI on PC script help needed

So I have managed to do Combat AI on PC character at NWN:EE where you still have control on your PC but if it sees enemies he starts to attack them and use spells/feats just like normal AI vs AI. I’m trying to do something at NWN2 too. One of the members on discord made a script for NWN:EE but obviously it doesn’t work for NWN2. So maybe someone is familiar with a code and maybe with some changes would be possible to do the same thing for NWN2? It should be a compiledscript where you just use a console command ‘‘dm_runscript xxx’’ and Combat AI should be on. Any ideas what needs to be changed to make it work? Here is a code:

void main()
{
    // Toggle AI scripts
    object oPC = GetFirstPC();
    if(GetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_BLOCKED_BY_DOOR) == "x2_def_onblocked")
    {
        SendMessageToPC(oPC, "Turning AI off");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_BLOCKED_BY_DOOR, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_DAMAGED, "");
        //SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_DEATH, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_DIALOGUE, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_DISTURBED, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_END_COMBATROUND, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_HEARTBEAT, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_MELEE_ATTACKED, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_NOTICE, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_RESTED, "");
        //SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_SPAWN_IN, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_SPELLCASTAT, "");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_USER_DEFINED_EVENT, "");
    }
    else
    {

        SendMessageToPC(oPC, "Turning AI on");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_BLOCKED_BY_DOOR, "x2_def_onblocked");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_DAMAGED, "x2_def_ondamage");
        //SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_DEATH, "x2_def_ondeath");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_DIALOGUE, "x2_def_onconv");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_DISTURBED, "x2_def_ondisturb");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_END_COMBATROUND, "x2_def_endcombat");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_HEARTBEAT, "x2_def_heartbeat");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_MELEE_ATTACKED, "x2_def_attacked");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_NOTICE, "x2_def_percept");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_RESTED, "x2_def_rested");
        //SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_SPAWN_IN, "x2_def_spawn");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_SPELLCASTAT, "x2_def_spellcast");
        SetEventScript(oPC, EVENT_SCRIPT_CREATURE_ON_USER_DEFINED_EVENT, "x2_def_userdef");
    }
}

I’m not the best at scripting, but taking a look at it, I see that the function SetEventScript doesn’t exist in NWN2. Maybe one could use SetEventHandler instead, I don’t know.

Before I got Error: UNDEFINED IDENTIFIER and changed SetEventScript to SetEventHandler and this error doesn’t appear but now I’m getting another error instead: Script1.nss (5): ERROR: DECLARATION DOESN’T MATCH PARAMETERS

some things to note

several scriptsets are defined in Nwn2_Scriptsets.2da w/ constants for them defined in nwscript.nss

// Brock H. - OEI 03/28/06 -- These must match the values in NWN2_ScriptSets.2da
int SCRIPTSET_INVALID               = -1;
int SCRIPTSET_NOAI                  = 0;
int SCRIPTSET_PCDOMINATE            = 1;
int SCRIPTSET_DMPOSSESSED           = 2;
int SCRIPTSET_PLAYER_DEFAULT        = 3; // These are the default scripts that are loaded onto the player character
int SCRIPTSET_COMPANION_POSSESSED   = 4; // The scripts that are applied on the player when he is controlled by
int SCRIPTSET_NPC_DEFAULT           = 9; // The default scripts for generic NPCs
int SCRIPTSET_NPC_ASSOCIATES        = 10; // The default scripts for NPC associates (summoned creatures, henchmen, etc)

applied w/

///////////////////////////////////////////////////////////////////////////////
// SetCreatureScriptsToSet
///////////////////////////////////////////////////////////////////////////////
// Created By:  Brock Heinz - OEI
// Created On:  09/23/05
// Modified:    12/19/05
// Description: Reassign all of the creature's scripts to a set specified by
//              the entry in the NWN2_ScriptSets.2DA file. If the creature
//              is not running his default scripts (for example, he is being
//              possessed by a DM), he won't start running the scripts from
//              the new set until he goes back to his default state
///////////////////////////////////////////////////////////////////////////////
void SetCreatureScriptsToSet( object oCreature, int nScriptSet );

A possessed character will not run any ai-events other than heartbeat, death, and userdefined (no matter what scripts have been slotted for it).

OnDialog is special in that if its slot is blank, the engine will run a default dialog script. So if you really want it blank, assign a non-existant ‘script’.

good luck. Here’s your script for nwn2 →

const string SCRIPT_no = "noscript";

void main()
{
    // Toggle AI scripts
    object oPC = GetFirstPC();
    if (GetEventHandler(oPC, CREATURE_SCRIPT_ON_BLOCKED_BY_DOOR) == "x2_def_onblocked")
    {
        SendMessageToPC(oPC, "Turning AI off");

        SetEventHandler(oPC, CREATURE_SCRIPT_ON_BLOCKED_BY_DOOR,    SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_DAMAGED,            SCRIPT_no);
//      SetEventHandler(oPC, CREATURE_SCRIPT_ON_DEATH,              SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_DIALOGUE,           SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_DISTURBED,          SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_END_COMBATROUND,    SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_HEARTBEAT,          SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_MELEE_ATTACKED,     SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_NOTICE,             SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_RESTED,             SCRIPT_no);
//      SetEventHandler(oPC, CREATURE_SCRIPT_ON_SPAWN_IN,           SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_SPELLCASTAT,        SCRIPT_no);
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_USER_DEFINED_EVENT, SCRIPT_no);
    }
    else
    {
        SendMessageToPC(oPC, "Turning AI on");

        SetEventHandler(oPC, CREATURE_SCRIPT_ON_BLOCKED_BY_DOOR,    "x2_def_onblocked");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_DAMAGED,            "x2_def_ondamage");
//      SetEventHandler(oPC, CREATURE_SCRIPT_ON_DEATH,              "x2_def_ondeath");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_DIALOGUE,           "x2_def_onconv");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_DISTURBED,          "x2_def_ondisturb");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_END_COMBATROUND,    "x2_def_endcombat");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_HEARTBEAT,          "x2_def_heartbeat");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_MELEE_ATTACKED,     "x2_def_attacked");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_NOTICE,             "x2_def_percept");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_RESTED,             "x2_def_rested");
//      SetEventHandler(oPC, CREATURE_SCRIPT_ON_SPAWN_IN,           "x2_def_spawn");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_SPELLCASTAT,        "x2_def_spellcast");
        SetEventHandler(oPC, CREATURE_SCRIPT_ON_USER_DEFINED_EVENT, "x2_def_userdef");
    }
}

 
see also: ginc_event_handlers #include

2 Likes

Thank you, kevL_s, pro as usual!

1 Like

@demoix

If I understand your requirements, I have done something like this in my own campaign. In that if you have AI on and are controlling a PC that is not your Main PC, then your Main PC will act with AI like a companion with AI on does.

The key point to note, however, as @kevL_s points out, is that any PC you currently possess (Main or not) will NOT do anything using AI because the player is possessing it.

There are quite a few other points you may or may not wish to consider when doing this, as it can become quite involved. If you want to see what I did, you can download my module (The Scroll) and take a look when v1.50E becomes available… NB: I am currently applying many updates to my module and is why it remains temporarily unavailable. It will be uploaded again as soon as I have completed the tests required for new updates.

Yes, you are 100% correct, I wish to let AI act and fight autonomously on my current selected PC. I tried things @kevL_s mentioned but seems doesn’t work as supposed. While this works fine at NWN1, it doesn’t work at NWN2. I did read somewhere that at NWN2 engine only heartbeat and a few more AI scripts can work on currently selected PC which doesn’t provide full AI abilities to fight enemies autonomously as companions do… I will keep an eye on your module.

The current workaround I made to get my PC to fight autonomously (even if there are no other companions to switch and the PC is alone) - to edit feat.2da and give every class a familiar option. So enemies 99% ignoring familiars and you just select it and your main PC fights autonomously as a companion would do. The only con is if the enemy throws AOE like fireball familiar dies and you are on your own until the next rest.

Maybe this…

// toggle_events
//	Toggles to either clear or restore all event handlers on uncontrolled main PC. 

#include "ginc_event_handlers"

void main()
{
	object oPC = GetFirstPC();
	object oCPC = GetFirstPC(FALSE);
	
	if (oPC == oCPC)
	{
		SendMessageToPC(oPC, "Aborted! Do not control main PC when running this.");
		SetEventsClearedFlag(oPC, FALSE);
		return;
	}
	
	if (!GetEventsClearedFlag(oPC))		
	{
		SaveEventHandlers(oPC);
		ClearEventHandlers(oPC);
		SendMessageToPC(oCPC, "Turning AI off for main PC");
	}
	
	else		
	{
		RestoreEventHandlers(oPC);
		DeleteSavedEventHandlers(oPC);
		SendMessageToPC(oCPC, "Turning AI on for main PC");
	}

	AssignCommand(oPC, ClearAllActions(TRUE)); 

// uncomment below code for debug messages			
/*	int n, nNum = GetNumScripts(oPC);
	
	for (n = 0; n < nNum; n++)
	{
		SendMessageToPC(oCPC, "Script " + IntToString(n) + " = " + GetEventHandler(oPC, n));
	}
*/	
}

Yes, there is a lot more going on I think (at least for what I needed anyway), as I developed my own system over a number of years, gradually refining it as I understood more. I even ended up rewriting the AI switch code for companions and adding my own xml stuff to help support it. Now it works very well, and it does what you say. It also allows multiple players to do this at the same time as they can also control a number of companions each.

Looking over my module (at next upload) will, hopefully, give you more ideas.

I tried this script, so it only disables AI on the main PC and also it requires other companions or familiar to switch? I mean the idea is to make the main PC turn AI on him while possessed so he can fight (using skills, feats, spells…) on his own while still, you controlling him (selected), like in Pillars of eternity 2 for example. Is it something that you did on your upcoming module?

@demoix idk

I remember trying to do more or less the opposite of what you want. To make PC (ie. controlled character ) not retaliate automatically when attacked … ( unless I deliberately clicked “okay, attack now” ). But i couldn’t find a way to bypass auto-retaliation, due to the combat-handlers not firing when a creature is controlled …

there’s def. hardcoded stuff to struggle against …

@demoix

Bear in mind you cannot have AI work if you are possessing the PC. The AI will only kick in after you switch to another PC.

@kevL_s

I believe I do have this in my AI off mode. That is PCs do nothing until ordered … even if attacked. I think they even stay still and take damage without retaliating, but I will check this when I get back to main computer. I have also not checked (or cannot remember rather) what happens if taunted and their AI is off.

I can double check specific tests if you let me know here. It was for micro management.

1 Like

… just walk up to a hostile with a controlled character. When the hostile attacks does PCC (player controlled character) attack back?

@kevL_s

OK, unfortunately (in your request), a player controlled PC does attack back (on a melee test attack). It is those PCs not currently controlled that do nothing until requested (inc the Main PC if currently unpossessed). But, if controlled, then they do defend themselves by automatically returning combat (inc possessed companions). i.e. It is being possessed that forces the returned combat.

I will keep an eye open to see if there is anything I can add to do what you need, but as you have not found anything, then I guess it is likely not doable as you say. It is not something I looked at this closely before as I always assigned retaliation for each round anyway. :innocent:

This reminds me of an issue I had when a PC was assigned a “use item” action - I could not immediately “Clear” that action either. Maybe it is tied alongside some hard coded animations? It’s as if the animation is given a priority to complete before any Clear action call does anything.

I don’t see animations as anything but eye-candy. They ought be, I think, dependent on actions, rather than the other way around. What’s likely happening is that the PC “uses action” and that once it started, it is, as if by definition, too late to stop it – the animation per se is only incidental. But yes there could be some sort of cooldown, and that cooldown could be determined by whether or not the animation is still playing …

anyway. I tried modifying Nwn2_Scriptsets.2da by adding a custom gb_player_attack script to #4 Companion_Possessed / ScriptAttacked. Then printed PC’s current AI scripts to chat →

on entering the game

scriptset1

after a dialog (default OnDialog handler gets assigned)

scriptset2

PC gets #3 Player_Default while controlling a Companion

scriptset3

and after re-controlling the PC

scriptset4

the gb_player_attack script showed up!

But it failed to execute debug-to-chat so it didn’t work :\

 
Oddly, during a combat with 2 party members (PC + Companion), I managed to control one of them who was being attacked and not auto-retaliate … but when trying to replicate the behavior I couldn’t get it to happen again … so, I guess it was just some quirk. Perhaps if an uncontrolled character is set not to retaliate, then player takes control if it while it’s being attacked, it still won’t retaliate … although when I specifically tried that, the character actually did retaliate soon after I took control.

It’s just another one of those things that requires a rewrite of the hardcode to sort out …

Anyway, i hope you (or someone) find how the engine assigns scriptsets a bit interesting

@kevL_s

I was considering playing around with that today to see if I could circumvent the same way. :grinning: So your results were good to see as it saved me repeating.

I definitely find this area interesting. I realised I had to understand more about the Nwn2_Scriptsets.2da some time back when I was allowing different players to control different companions. Things got really involved then and I had to reassign many scripts in that 2da for PCs, companions. summons and henchmen, because they all were affected by the system I was working on.

Agreed. It just felt the mechanic behind this point of play (action with animation) may have been “connected”. i.e. Find one and I find the other. A bit like finding an animation in a 2da sort of thing, where the 2da may also point to a type of “cooldown” (stop interrupt) in the same line. I never found anything to date though.

This may be the case, as I believe this happens in my system too… although, as I also have some code that tries to keep a PC defending themselves if attacked, then it gets in the way of a pure result.

SOME IRRELEVANT POST DELETED TO KEEP TIDY

UPDATE: There may be a slim possibility to use my current pause system XML call back to force a ClearAllActions(TRUE) for an attacked PC. It may be a sledgehammer to crack a nut, but it makes use of an XML that is always firing in the background, which may be able to monitor when a PC is attacked and prevent them from retaliating. Here is the gist of it in the OC edited xml … The gui_updatetargets is where all the work then takes place. (This script has been updated quite a few times by me, but it may be where I once stopped all actions … I am trying to see if that is what I once did …but you may beat me to it if you see what this is basically doing.)

UPDATE RESULT TEST: This does work. Basically, I quickly edited my own target system to simply clear all actions when called, and now even the possessed PC does nothing if attacked. i.e. Their actions are cleared down before they have time to react. So, I imagine it would be easy enough to add a variable to bypass the clearing if you wish to maintain the retaliation. Hmmm … You made me think now, whether I need to consider removing any retaliation myself if in no AI mode and no commands have been given. i.e. Is it better to allow a possessed PC to defend themselves if attacked or just take it?

noticewindow.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Neverwinter Nights 2                     -->
<!-- Copyright � Obsidian Entertainment, Inc. -->

<UIScene name="SCREEN_NOTICEWINDOW" x=0 y=0 width="SCREEN_WIDTH" height="SCREEN_HEIGHT" 
         capturemouseevents=false capturemouseclicks=false ignoreevents="true"
         scriptloadable="true" priority="SCENE_SCRIPT" fullscreen="true" />

	<UIText name="NOTICE_TEXT" x=ALIGN_CENTER y=68 width=800 height=200 fontfamily="Special_Font" style="2" multiline=true maxlines=3
	        align=center valign=top uppercase=true hidden=true update=true ignoreevents="true" 
	        capturemouseevents=false capturemouseclicks=false color="yellow" scalewithscene=true
	        OnUpdate=UIText_OnUpdate_DisplayNoticeText(3.0,5.0) />

	<UIText name="PAUSE_NOTIFICATION" x=0 y=ALIGN_CENTER width=1024 height=100 fontfamily="Special_Font" style="4" scalewithscene=true 
		align=center valign=middle uppercase=true text="Game Paused" hidden=true ignoreevents="true"
        capturemouseevents=false capturemouseclicks=false
		update=true updaterate="0.1" OnUpdate=UIText_OnUpdate_ShowPauseNotification() OnUpdate0=UIObject_MISC_ExecuteServerScript("gui_updatetargets","TESTUNPAUSED")/>

@kevL_s

UPDATE: I have been able to make the target “switchable” by unlocking the “do nothing” when the player right-clicks a target, which then updates its target to attack. At which point the system keeps the target until this is changed. I am looking at “losing” the target for the possessed PC if they move to a location. Testing … Yes, moving as well as removing the attack from the attack queue will now both stop a retaliation until the player right-clicks and retargets the enemy they wish to attack. Yeh! Although that all done, maybe I ought to have a button (option) to allow automatic retaliation as an option, without needing to right click attack target first. :sweat_smile:

Just to add a final note: This is to show a proof of concept, but it does currently works with my own turn-based system, which auto-pauses and uses a GUI feedback for current target and actions of the PCs. It works, but I am not going to use it as it currently stands as I would need to spend more time fixing some target GUI feedback text, and as my players would not need this facility I am commenting it out in my own code as a future “maybe” if I get more time to tidy it all up. :slight_smile: I leave it in (commented) for those who may be interested in working more with it. @kevL_s If you want more of the code I used within the script that the XML called to, then let me know. Basically, it was clearing all actions unless the player had right-clicked and setup a “valid target” that way for them.

1 Like

@kevL_s , from my side I can add, something that might be interesting for you in regards to PC won’t fight back. Even if PC or companions is attacked they won’t even fight back. I noticed this bug once I downloaded this pack Kaedrin's PrC + TCC + Spell Fixes + Tome of Battle + Races of Faerun + Light Emitting Spell Effects | The Neverwinter Vault and you companions just stand still… I’m not sure what is the problem in the script but you should figure it out yourself. The only thing I found and fixed myself to fight them back again was by deleting one file from the whole pack which is cmi_player_equip.NCS. Hope it helps a bit

1 Like

This (it appears to me) is the default behaviour for unpossessed PCs when AI is off. Furthermore, the same unpossessed PCs would still not attack when a broadcast command to attack is issued, which appeared to be more of a bug to me (even if it was meant to be intended). I recently updated my AI system to ensure all PCs attacked when a broadcast command was issued whether AI was on or off. In my situation, the AI being on or off now simply determines if the PC in question will use their own AI to determine their attacks of just keep attempting a melee attack until ordered to stop. It’s more intuitive in my opinion.

However, you will probably find that (as KevL pointed out) that a possessed companion will always fight back even when you do NOT want them to. So, my guess is that unless that the bug you mention is more likely to relate to another aspect, and my guess would be that the possessed PC would probably still fight back if attacked. :thinking: Although, I have currently found a way to stop even the possessed PC attacking (see the above post), but am now trying to make it more easily (intuitively) switchable.

ok i got auto-retaliate to stop by calling ClearAllActions() from the ActionQueue gui.

@Lance so … how do you figure I capture a mouseclick on an enemy to turn it off …

this is what i got so far →

// 'gui_antiretaliation'
/*
	Gui script. Fires from "actionqueue" with an updaterate of 0.1 sec

	Stops player controlled characters from auto-retaliating when attacked.
*/

// OBJECT_SELF is the MainPC of the player whose gui fired.
void main()
{
	if (!GetPause())
	{
		object oPcc = GetControlledCharacter(OBJECT_SELF);

		if (GetCurrentAction(oPcc) == ACTION_ATTACKOBJECT)
		{
			AssignCommand(oPcc, ClearAllActions(TRUE));
		}
	}
}

 

yes. is that even a question?  :p

1 Like

@demoix unfortunately packs such as that require a lot of update/maintenance to remain in sync …