ActionDoCommand: Superfluous or Essential?

I understand that the AssignCommand function will cause a target Creature to perform some sort of Action, but in most scripts, I also see ActionDoCommand as a wrapper around the Action for the Creature to perform.

What exactly does ActionDoCommand do? What differs if I pass the void-function invocation directly to AssignCommand?

What differs between the following invocations?

AssignCommand(oActor, ActionDoCommand(SendMessageToPC(GetFirstPC(), "Message")));
AssignCommand(oActor, SendMessageToPC(GetFirstPC(), "Message"));

ActionDoCommand() turns a functioncall into an action on the ActionQueue

eg.

ActionMoveToLocation(lDest);
ActionDoCommand(SendMessageToPC(GetFirstPC(), "tada, OBJECT_SELF arrived at lDest"));

AssignCommand() on the other hand makes oActor the owner of a functioncall: oActor becomes OBJECT_SELF for the purpose of the function.

eg.

AssignCommand(oActor, ActionMoveToLocation(lDest));
AssignCommand(oActor, ActionDoCommand(SendMessageToPC(GetFirstPC(), "tada, oActor arrived at lDest")));

 
( neither function, iirc, accepts anything other than a void return for the aAction arg. So if you want to assign or do a non-void-returning function, it needs to be wrapped in a void-returning helper function first )

In your examples

AssignCommand(oActor, ActionDoCommand(SendMessageToPC(GetFirstPC(), "Message")));

assigns oActor as OBJECT_SELF for the purpose of the ActionDoCommand(). If oActor has an empty ActionQueue the SendMessageToPC() executes right away. But if oActor already has some stuff in the ActionQueue, those actions need to finish before the message will be sent.

AssignCommand(oActor, SendMessageToPC(GetFirstPC(), "Message"));

This executes the SendMessage immediately, and wrapping it with AssignCommand is irrelevant since it doesn’t matter what OBJECT_SELF is, as far as SendMessageToPC() is concerned.

 
both are essential, in the right places …

2 Likes

This is a good write-up. I’d only like to add few things.

It’s important that with ActionDoCommand (and DelayCommand) “right away” means “right after current script terminates”. Commands may therefore arrive “out-of-order” from player’s perspective (compare SpeakString to ActionSpeakString). This behavior can be used to pull off tricks (i.e. with SetCommandable), but if care is not taken, they can lead to nasty surprises.

Here yes, but since OBJECT_SELF is de facto a pointer, when one wants to affect the OBJECT_SELF from current context (not oActor but AssignCommand caller through oActor), to avoid yet another pitfall, it is recommended to save the OBJECT_SELF in a variable and pass it as such.

Final issues: if you do ActionDoCommand(oFoo, MyAction(One(), Two()), One() and Two() are called and their results collected when MyAction is to be called, NOT in current script. In other words: a lot may happen in the meantime. This may lead to hard-to-debug code and is the reason why care must be exerted when passing OBJECT_SELF around.

The example below covers all three cases mentioned here:

void main()
{
    object oSelf;
    object oGobbo;

    oSelf = OBJECT_SELF; // PC
    oGobbo = GetObjectByTag("GOBBO"); // some goblin

    // Outputs: OBJECT_SELF = oGobbo (caution), executed after script
    AssignCommand(oGobbo, ActionDoCommand(
        SpeakString("OBJECT_SELF = " + GetName(OBJECT_SELF))));

    // Outputs: OBJECT_SELF = oSelf (variable), executed after script
    AssignCommand(oGobbo, ActionDoCommand(
        SpeakString("OBJECT_SELF = " + GetName(oSelf))));

    // Executed within context of this script, spoken before goblin
    AssignCommand(OBJECT_SELF, SpeakString(
        "I, " + GetName(OBJECT_SELF) + " speak before the puny goblin"));

    // Set module variable to 3
    SetLocalInt(GetModule(), "my_var", 3);

    // This will make OBJECT_SELF speak my_var = 5, NOT my_var = 3
    ActionDoCommand(SpeakString(
        "my_var = " + IntToString(GetLocalInt(GetModule(), "my_var"))));

    // Set module variable to 5 - registered before above SpeakString
    SetLocalInt(GetModule(), "my_var", 5);
}

Output:

2 Likes

The Lexicon is also a good place to look for this sort of information:

https://nwnlexicon.com/index.php?title=ActionDoCommand

1 Like

You can also occasionally use ActionDoCommand to first assign a set of actions to a creature, finishing with an ActionDoCommand(SetCommandable(TRUE)); and then lock their action queue at a mini-delay.

    ClearAllActions();
    ActionWait(1.0);
    ActionPlayAnimation(ANIMATION_LOOPING_GET_LOW, 1.0, 3.0);
    ActionWait(1.0);
    ActionSpeakString("I found something!");
    ActionWait(1.0);
    ActionDoCommand(SetCommandable(TRUE));
    DelayCommand(0.01, SetCommandable(FALSE));

It’s convenient when you need to force players to do something without being able to be interrupted and the time it will take is uncertain. Leaves them helpless, too, though, so it’s something to use with care.

1 Like