Cancel current action / alternative to moving in a cutscene

So I basically wrote a cutscene I am proud of. Everything is working. Everything? No, a small detail while aborting the cutscene withstands my perfectionism.

The issue is this: In this cutscene I wanted to be clever. Since NWN doesn’t allow you to have nice things like a normal camera movement, I made the PC invisible and use him to get the camera around while showing a copy of the PC to the player. So far so normal.

The problem seems to be that aborting the cutscene while the PC is moving (with GestaltActionMove) to get a nice camera transition, the movement action is still completed which of course uncovers to the player what mumbo jumbo I am doing under the hood. I can’t seem to clear the movement, because it is no longer in the action queue (?). Normally you would probably destroy the moving NPC and respawn it, but I can’t do that with the PC without severly changing his OnDeath, can I?

This seems to be solvable by either don’t have the cutscene be skipable or by iterating the movement, by calculating the required distance and issuing multiple Moveactions to the queue. While the first is just not a solution, the latter probably causes the fluid motion needed for the camera to stutter. Also it seems to be incredibly complicated for just moving the PC in a cutscene.

Which brings me to my question: What is the easy solution I am overlooking or should I adopt the rule of thumb to never move the pc with ActionMove in cutscenes?

Cutscene script
#include "in_g_cutscene"

void main()
{
      object oPC = GetFirstPC();
      object oLester = GetObjectByTag("Lester");

      location lPortal = GetLocation(GetWaypointByTag("tp_1p_portal"));
      location lPortalOPC = GetLocation(GetWaypointByTag("tp_1p_portal_opc"));
      location lPC = GetLocation(GetWaypointByTag("tp_1p_opc"));
      location lLester = GetLocation(GetWaypointByTag("tp_1p_lester"));

      object oWaypointPortal = GetWaypointByTag("tp_1p_portal");
      object oWaypointMagistra = GetWaypointByTag("tp_1p_magistra");
      object oWaypointPC = GetWaypointByTag("tp_1p_opc");
      object oWaypointLester = GetWaypointByTag("tp_1p_lester");
      object oWaypointPCMove = GetWaypointByTag("tp_1p_portal_opc_move");

      // start cutscene
      float fLength = 45.0;
      GestaltStartCutscene(oPC, "cutscene_prison", TRUE, TRUE, TRUE, TRUE, TRUE);
      GestaltCameraFade(0.0, oPC, FADE_IN, FADE_SPEED_SLOW, 1.5);

      // copy PC and move Lester to lLester
      object oNew = CopyObject(oPC, lPC, oPC, "PC_Copy");
      AssignCommand(oLester, ActionJumpToLocation(lLester));

      // make PC invisble, move-through and send him to the portal
      effect eInvisible = EffectVisualEffect(VFX_DUR_CUTSCENE_INVISIBILITY);
      effect eGhost = EffectCutsceneGhost();
      ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eInvisible, oPC, fLength);
      ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eGhost, oPC, fLength);
      AssignCommand(oPC, ActionJumpToLocation(lPortalOPC));

      // camera movement
      GestaltCameraFacing  (0.0,
                            90.0, 10.0, 100.0,
                            oPC);

      // animate portal
      GestaltCreate(2.0, oWaypointPortal, OBJECT_TYPE_PLACEABLE, "plc_portal", "oPortal");
      effect ePortal = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_3);
      GestaltApplyLocationEffect(2.0, lPortal, ePortal, INSTANT);
      //DelayCommand(2.0, SoundObjectPlay(GetObjectByTag("MagicPortalNeutral")));
      GestaltSoundObject(2.0, GetObjectByTag("MagicPortalNeutral"));

      // spawn and move magistra
      GestaltCreate(2.0, oWaypointPortal, OBJECT_TYPE_CREATURE, "magistra", "oMagistra");
      effect eMagistra = EffectVisualEffect(VFX_DUR_PROT_PREMONITION);
      GestaltApplyEffect(2.1, OBJECT_INVALID, eMagistra, PERMANENT, 0.0, "oMagistra");
      GestaltTagActionMove(2.1, "oMagistra", oWaypointMagistra);
      GestaltTagActionFace(3.0, "oMagistra", 270.0);

      // zoom camera on Magistra
      GestaltCameraMove (4.0,
                         90.0, 10.0, 100.0,
                         90.0, 6.0, 150.0,
                         19.0, 20.0, oPC);

      // Magistra monologue
      GestaltTagActionSpeak(4.0, "oMagistra", "<cdے>Greetings, prisoners! I am Magistra Aelia, a Pit Master.</c>", NORMAL, 5.0);
      GestaltTagActionSpeak(9.0, "oMagistra", "<cdے>I will invoke you sequentially and you will join me at once.</c>", NORMAL, 5.0);
      GestaltTagActionSpeak(14.0, "oMagistra", "<cdے>If you do not behave as expected, you will be killed.</c>", NORMAL, 5.0);
      GestaltTagActionSpeak(19.0, "oMagistra", "<cdے>Prisoner Number 1374, " + GetName(oPC) + ". Come over here swiftly.</c>", ANIMATION_FIREFORGET_READ, 0.0, 0.5);
      GestaltActionOpen(33.0, GetObjectByTag("CellDoor"), GetObjectByTag("CellDoor"));

      // Turn Camera on oPC
      GestaltCameraMove (25.0,
                         90.0, 8.0, 150.0,
                         270.0, 1.0, 80.0,
                         10.0, 50.0, oPC);

      // move invisible oPC to simulate camera movement
      GestaltActionMove(25.0, oPC, oWaypointPCMove, FALSE, 0.0, 10.0);

      // Lester speaks to oPCand the cell door opens
      GestaltActionFace(35.0, oLester, 0.0, 2, oNew);
      GestaltActionFace(35.0, oNew, 0.0, 2, oLester);
      GestaltActionSpeak(35.0, oLester, "<cŒŒŒ>Hey, you are the first one! That's gotta be lucky!</c>", ANIMATION_FIREFORGET_VICTORY2, 0.0);

      // final steps
      GestaltActionMove(38.0, oNew, oWaypointPCMove);
      GestaltCameraFade(43.0, oPC, FADE_CROSS, FADE_SPEED_SLOW, 1.9);
      GestaltDestroy(44.0, oNew);
      GestaltActionFace(44.0, oPC, 90.0);
      GestaltExecuteScript(44.9, oPC, "_tp_1p_pheartbea");

      // close cutscene
      GestaltStopCutscene(fLength, oPC);
}
Abort script
#include "in_g_cutscene"

void main()
{
      // determine which cutscene is to be cancelled
      object oPC = GetLastPCToCancelCutscene();
      string sName = GetLocalString(oPC, "cutscene");

      if (sName == "cutscene_prison")
      {
            // cancel prison cutscene
            GestaltCameraFade(0.0, oPC, FADE_IN, FADE_SPEED_FASTEST);
            GestaltSetSpeed(0.0, oPC, 0.1, 100.0, 0);
            GestaltSetSpeed(0.1, oPC, 0.0, 4.0, 0);
            DeleteLocalString(GetModule(),"cutscene");
            GestaltStopCutscene(0.2, oPC, "tp_1p_portal_opc_move");
            AssignCommand(GetObjectByTag("Lester"), ActionJumpToLocation(GetLocation(GetWaypointByTag("tp_1p_lester"))));
            DestroyObject(GetObjectByTag("oPortal"));
            CreateObject(OBJECT_TYPE_PLACEABLE, "plc_portal", GetLocation(GetWaypointByTag("tp_1p_portal")), FALSE, "oPortal");
            SoundObjectPlay(GetObjectByTag("MagicPortalNeutral"));
            DestroyObject(GetObjectByTag("oMagistra"));
            CreateObject(OBJECT_TYPE_CREATURE, "magistra", GetLocation(GetWaypointByTag("tp_1p_magistra")), FALSE, "oMagistra");
            effect eMagistra = EffectVisualEffect(VFX_DUR_PROT_PREMONITION);
            ApplyEffectToObject(DURATION_TYPE_PERMANENT, eMagistra, GetObjectByTag("oMagistra"));
            AssignCommand(GetObjectByTag("CellDoor"), ActionOpenDoor(GetObjectByTag("CellDoor")));
            DestroyObject(GetObjectByTag("PC_Copy"));
            //AssignCommand(oPC, ClearAllActions());
            //AssignCommand(oPC, ActionJumpToLocation(GetLocation(GetWaypointByTag("tp_1p_portal_opc_move"))));
            ExecuteScript("_tp_1p_pheartbea", oPC);
      }
      // continue with further "else if" for any skiappable cutscene. Final else follows.
      else
      {
            // this should never happen
            SendMessageToPC(oPC, "You cannot cancel this cutscene(?)");
      }
}

The problem is the 25 second delay before moving the PC.

If the cutscene is cancelled before that, the command is in the delayed command queue, not the PC’s action queue. So, even if you cancel the PC’s action queue, the move command is still added to their action queue at the appointed time. No doubt this will happen after Gestalt has done its end cutscene clear up, too.

I don’t think there’s any trivial way to cancel a delayed command. It’s fire-and-forget.

However, you can work around this. Instead of issuing GestaltActionMove, execute a new script with a 25 second delay. That script needs to check whether the cutscene has been aborted, then, if not, GestaltActionMove with no delay.

This is a perfectly general method to gain control over delayed commands and stop them if need be.

I find that it’s such a major issue when cancelling cutscenes that it’s better to write them as a recursive script. Whenever a delay is called for, the script restarts itself with that delay. A counter keeps track of which step to execute next. That way, the script can stop itself as soon as it detects the cutscene is aborted.

1 Like

I may have not correctly made clear what the problem is: The problem is not that the cutscene is over and the PC starts suddenly moving. AFAIK the Gestalt actions do not trigger if the cutscene is not running anymore. Atleast there are checks in place to drop all Gestalt commands if there is no running cutscene. The problem was that the move action was already triggered and the PC continued moving even though the cutscene was over.

I have been tinkering with this for a while and this is the start of the abort script I came up with:

            // cancel prison cutscene
            GestaltCameraFade(0.0, oPC, FADE_IN, FADE_SPEED_FASTEST);
            GestaltClearActions(0.0, oPC);
            GestaltJump(0.1, oPC, GetWaypointByTag("tp_1p_portal_opc_move"));
            DeleteLocalString(GetModule(),"cutscene");
            GestaltStopCutscene(0.2, oPC, "tp_1p_portal_opc_move");

This is where the trouble starts now for me. In 50% of cases it works, in 50% of cases it doesn’t. Rarely the camera is not reset, which leads me to believe that GestaltStopCutscene is not orderly executed, but that is not the case every time. Sometimes the camera resets and the jump does’nt happen. And sometimes both works 5 times in a row. Well atleast it now works 50% of the time instead of never. Yay.

In theory the ClearActions and Jump lines are redundant, because the GestaltStopCutscene should do both these things. In pratice I have no idea why this is happening. I cannot really test any solution because it might just work a few times just to troll me.

At first I thought deleting the local string is at fault and blocks the Gestalt commands from happening, but I tried delaying it by 0.5 seconds or completely leaving it out. Nothing changes. Also in the official examples the string is deleted before the cutscene is ended. But why does GestaltStopCutscene stop working?

You wrote on the NWVault page that there seem to be some bugs when aborting the cutscene. Is this what you were talking about?