You are here

OnDeath script - should work but doesn't

44 posts / 0 new
Last post
andgalf
OnDeath script - should work but doesn't

Hi everybody. I have an OnDeath script that should fire but doesn't, and I don't know why. The situation is this: If you kill a particular pixie (that's the creature with the OnDeath script), then it will spawn a conversation between the PC and a child called Ginnie, who is nearby. The script looks like this:

 

#include "ginc_cutscene"


//
void main()
{
    object oGinnie = GetObjectByTag("Ginnie");

    AssignCommand(oGinnie, ClearAllActions(TRUE));
    MakeConversable(oGinnie);


    object oPC = GetOwnedCharacter(oPC);

    AssignCommand(oPC, ClearAllActions(TRUE));
    

    

    DelayCommand(2.f, AssignCommand(oGinnie, ActionStartConversation(oPC, "ginnie_conv")));
}

 

Maybe it's the "#include" that is the problem? I just saw I haven't checked that out yet.

I know a lot of you guys are experts at scripting, so if you have the time, could you please help me out with this? Thanks!

By the way, the compiler doesn't protest when saving and compiling the script.
 

  • up
    50%
  • down
    50%
kevL's

at a guess, try changing this:

    DelayCommand(2.f, AssignCommand(oGinnie, ActionStartConversation(oPC, "ginnie_conv")));

to this:

    AssignCommand(oGinnie, DelayCommand(2.f, ActionStartConversation(oPC, "ginnie_conv")));


because OBJECT_SELF doesn't have 2 seconds to pass ownership off to Ginnie ... its dead jim
 

  • up
    50%
  • down
    50%
andgalf

Hehe. That makes sense. I'll try that. Thanks!

  • up
    50%
  • down
    50%
TheBarbarian

I'd suspect this line, since you're trying to define who oPC is by using itself before it's been defined as anything.

object oPC = GetOwnedCharacter(oPC);

But we don't have GetOwnedCharacter in NWN1, so no guarantees. :-/

  • up
    100%
  • down
    0%
Claudius33

The Barbarian is right the line  object oPC = GetOwnedCharacter(oPC); looks supicious. Though I haven't used that function yet.

Firing a convo in OnDeath script is very hazardous. The PC might be unconscious, not close enough to Ginnie, or can't see Ginnie. The fight could be still in progress unless the pixie is the only foe. Besides a delay of 2 seconds is enough for the player to click anywhere killing the convo.

If you want do to it make sure the fight is not in progress (change faction of all remaining enemies to STANDARD_FACTION_COMMONER for instance, ClearAllActions(TRUE), ForceRest your party, teleport the PC to Ginnie, then fire the convo with a tiny delay. 0.15f or .2f should do. So quite an elaborated script.

As for me, I'd prefer to update the journal telling the player to speak to Ginnie asap which is simplier and safer IMHO.

  • up
    100%
  • down
    0%
andysks

Firing convos OnDeath is indeed dangerous and I have witnessed it often. In my experience, the best and safest way to do such a thing is by spawning an ip-speaker to control the conversation. The functionality of the ip-speaker is quite technical so I will not go in detail. Good news, there is a header for it. So all you need to use in your script are these lines.

 

#include "ginc_ipspeaker"
 
void main()
{
  object oPC = GetFirstPC();
 
  CreateIPSpeaker("tag_of_speaker", "name_of_convo", GetLocation(GetFirstPC()), 1.0f);
}
  • up
    50%
  • down
    50%
kevL's

yep +2

although i have seen CreateIPSpeaker() fail, its extremely rare in my xp. It should be considered the "go to" for all dialog. (Although in many cases it's just overhead - but if you don't want to get twiddly, it's like a shotgun instead of a sniper rifle)

  • up
    50%
  • down
    50%
andysks

In my experience it only fails when called implicitely from GroupOnDeathConversation(), or however the function is called. But this could also be because of group mechanics (how I add creatures in the group and stuff like that). Although the group functions are also quite stable. From my knowledge, the reason the ip-speaker works is because the ip-hb checks again and again if the convo has fired and tries to fire it if it hasn't. 

 

 

Edit: I just remembered something. If you leave GetFirstPC() like that, it will not fire the convo if a companion is selected. However, the convo will fire the moment you select your main PC because the hb is constantly checking. If you change it to GetFirstPC(FALSE), then it will fire for companions too. The problem for me was, sometimes for story reasons an NPC might only want to talk to the main PC and I couldn't find a way to change selection to the main PC if a companion initiated a convo with CreateIPSpeaker(). Just small information. 

I guess it is not a big deal. One can always put a line of the convo saying "Thanks for helping me kill this pixie. Now I need to talk to your leader". But I was a perfectionist and wanted an automatic switch which couldn't happen.

  • up
    50%
  • down
    50%
kevL's

GroupOnDeathBeginConversation() is somewhat less reliable than straight-up CreateIPSpeaker() imo. Personally if i want something to happen, will use GroupOnDeathExecuteCustomScript(), which seems very robust; and i'll invoke a dialog in the custom script. The pseudo-hb that CreateIPSpeaker() starts is nice, but not required: control *can* be switch to the main PC immediately, and a dialog started, eg.

just some notes /shrug

  • up
    50%
  • down
    50%
andgalf

Wow! Thanks for all the replies guys!laugh

Indeed, the first thing that kevL's suggested didn't work. I guess I'll try that CreateIPSpeaker thing. I'll be back.

  • up
    100%
  • down
    0%
andgalf

Your script worked beautifully andysks! Thank you so much! The script looks like this now:

 

#include "ginc_ipspeaker"
 
void main()
{
  object oPC = GetFirstPC(FALSE);
 
  CreateIPSpeaker("Ginnie", "ginnie_conv", GetLocation(GetFirstPC()), 1.0f);
}

 

What would I do without the community...smiley

  • up
    100%
  • down
    0%
Shallina

"DelayCommand(2.f,  " Doesn t work beceause the object supposed to run it doesn t exist anymore. A dead object can only run "instant script"

 

in order to be sure it works with a delay here what you must do :

 

ExecuteScript(GetModule(),"my_new_script"');

 

This way the "new script" will be run by the module whitch isn"t "dead" and isn t "shut down". Inside this new script you can use a delaycommand without any problem or what ever you want..

  • up
    50%
  • down
    50%
andgalf

Ok, so now I have another OnDeath script problem. In another situation in the module I wanted to teleport the party if they die. So I made a script as usual with Lilac Soul's Script Editor. However, when the party dies nothing happens. I placed this script in the On Player Death Script in Module Properties:

/*   Script generated by
Lilac Soul's NWN Script Generator, v. 2.3

For download info, please visit:
http://nwvault.ign.com/View.php?view=Other.Detail&id=4683&id=625    */

//Goes OnPlayerDeath of the module properties
void main()
{

object oPC = GetLastPlayerDied();

if (!GetIsPC(oPC)) return;

int nInt;
nInt=GetLocalInt(oPC, "NW_JOURNAL_ENTRYmeegan_quest");

if (nInt < 11)
   return;

object oTarget;
location lTarget;
oTarget = GetWaypointByTag("blessed_wp");

lTarget = GetLocation(oTarget);


if (GetAreaFromLocation(lTarget)==OBJECT_INVALID) return;

oTarget=GetFirstFactionMember(oPC, FALSE);

while (GetIsObjectValid(oTarget))
   {
   AssignCommand(oTarget, ClearAllActions());

   AssignCommand(oTarget, ActionJumpToLocation(lTarget));
   oTarget=GetNextFactionMember(oPC, FALSE);
   }

}

 

I've also, for a few hours now, tried to incorporate this script into the script that I used before in the module in On Player Death Script (the script below). When that didn't work I tried the above (only), just to make sure that worked, but obviously there is something wrong with the above script. The script below is working in the module (but I would like to incorporate the one above so that in a unique situation when the party dies they get teleported to a new area).

 

// k_mod_player_death
/*
   Module OnDeath handler to queue KnockOut script for owned PCs
*/
// BMA-OEI 12/19/05
// BMA-OEI 2/28/06 -- DeathScript support
// BMA-OEI 8/10/10 -- Exit if not owned by player (e.g. possessed companions)


#include "ginc_death"
#include "ginc_debug"
    

void main()
{
    object oDead = GetLastPlayerDied();
    PrintString( "k_mod_player_death: " + GetName(oDead) + " executing OnPlayerDeath event" );

    // Abort if dying character is not an owned PC
    if ( GetIsOwnedByPlayer( oDead ) == FALSE )
    {
        PrintString( "** k_mod_player_death: " + GetName(oDead) + " not owned by a player. ABORT!" );
        return;    
    }
    
    // Check for additional death script
    string sDeathScript = GetLocalString( oDead, "DeathScript" );
    if ( sDeathScript != "" ) ExecuteScript( sDeathScript, oDead );    
    
    // Queue knockout script
    AssignCommand( oDead, KnockOutCreature( oDead ) );
}

  • up
    50%
  • down
    50%
andysks

Just by looking at it I cannot figure out the problem. I would need to test extensively. But as we said before, OnDeath can act weird. One trick I use often when my scripts don't work, is make a conversation instead. For example, the CreateIPSpeaker works fine, right? Then call a conversation OnDeath that goes like...

 

"You die and everything goes dark..."

Blah blah blah

 

Then when the player clicks end conversation, call an action script that never fails, like ga_jump_party_in_formation.

 

You get the same result with some DM storytelling in there too.

  • up
    50%
  • down
    50%
andgalf

Interesting idea. I'll try that.

  • up
    50%
  • down
    50%
andgalf

andysks - That actually worked. Even if it's not as slick as my original idea it works almost just as well. Thank you for the idea!

This is how the script looks now:

#include "ginc_ipspeaker"
#include "ginc_death"
#include "ginc_debug"

 
void main()
{
 
 
  object oPC = GetLastPlayerDied();

if (!GetIsPC(oPC)) return;

int nInt;
nInt=GetLocalInt(oPC, "NW_JOURNAL_ENTRYmeegan_quest");

if (nInt > 10)
   
  {
  CreateIPSpeaker("", "death_convo", GetLocation(GetFirstPC()), 1.0f);
  }
 
  else
 {
     object oDead = GetLastPlayerDied();
      PrintString( "k_mod_player_death: " + GetName(oDead) + " executing OnPlayerDeath event" );

    // Abort if dying character is not an owned PC
    if ( GetIsOwnedByPlayer( oDead ) == FALSE )
    {
        PrintString( "** k_mod_player_death: " + GetName(oDead) + " not owned by a player. ABORT!" );
        return;    
    }
    
    // Check for additional death script
    string sDeathScript = GetLocalString( oDead, "DeathScript" );
    if ( sDeathScript != "" ) ExecuteScript( sDeathScript, oDead );    
    
    // Queue knockout script
    AssignCommand( oDead, KnockOutCreature( oDead ) );
}
}

  • up
    50%
  • down
    50%
andgalf

Hi again. I noticed a bug in my latest script here. If the controlled character at the moment of death isn't the PC the conversation wont trigger. Is there any way around that?

Ah, thinking a little I think I get it. I need:

object oPC = GetFirstPC(FALSE); 

right? 

  • up
    50%
  • down
    50%
kevL's

hi andgalf, you're asking things that are more complicated than i think you realize yet:

the module's OnPlayerDeath event fires only when an OwnedPC dies (whether controlled or not). what i think you're looking for, also, is the script that fires when a controlled character dies: 'gb_comp_death' ...

note: Nwn2_Scriptsets.2da is what actually defines what script fires when a controlled character dies, and by default is 'gb_comp_death' ... which also fires when a companion dies (whether controlled or not).

getting the two scripts/events to work in sync, like Andy said, requires extensive testing,


and ofc things depend on whether you want the dialog to start when only the true PC dies, or when the entire party is deceased. (barring Associates since they can't really do much..)

so, uh, yeah, it's difficult to give advice without a bunch of testing, along with varied and specific details

  • up
    50%
  • down
    50%
andysks

I would look into the campaign Dark Waters. The author did something similar there and perhaps you can take it from there. 

  • up
    50%
  • down
    50%
andgalf

Thanks for the replies guys! Yes, I'm beginning to understand this was more complicated than I thought. Right now things only work if one controls the PC and not the companions, even though I tried with the object oPC = GetFirstPC(FALSE); thing.

What I am after really is that when the PC dies, not necessarily the companions, the script and the conversation fires, but it has to fire even if the player controls a companion at the moment. I hope it's doable.

I have Dark Waters somewhere on my harddrive. Will look into it.

 

My script looks like this now:

 

#include "ginc_ipspeaker"
#include "ginc_death"
#include "ginc_debug"

 
void main()
{
 
 
      object oPC = GetLastPlayerDied();

    if (!GetIsPC(oPC)) return;

    int nInt;
    nInt=GetLocalInt(oPC, "NW_JOURNAL_ENTRYmeegan_quest");

    if (nInt > 10)
   
      {
     object oPC = GetFirstPC(FALSE);
      CreateIPSpeaker("", "death_convo", GetLocation(GetFirstPC()), 1.0f);
      }
 
      else
     {
     object oDead = GetLastPlayerDied();
      PrintString( "k_mod_player_death: " + GetName(oDead) + " executing OnPlayerDeath event" );

    // Abort if dying character is not an owned PC
    if ( GetIsOwnedByPlayer( oDead ) == FALSE )
        {
        PrintString( "** k_mod_player_death: " + GetName(oDead) + " not owned by a player. ABORT!" );
        return;    
        }
    
    // Check for additional death script
    string sDeathScript = GetLocalString( oDead, "DeathScript" );
    if ( sDeathScript != "" ) ExecuteScript( sDeathScript, oDead );    
    
    // Queue knockout script
    AssignCommand( oDead, KnockOutCreature( oDead ) );
    }
}

 

Or is there some other way to do this maybe? I'm thinking, maybe the PC doesn't have to be dead but just close to death. Would that be easier to achieve?

  • up
    50%
  • down
    50%
kevL's

well ... this line:

    if (!GetIsPC(oPC)) return;

is saying if the LastPlayerDied is not controlled exit the script without doing anything. Try commenting it out so that the rest of the script will run for uncontrolled PCs.




ps. am not so sure that the use of an empty string for sSpeakerTag is healthy,
I think it's just relying on the fact that most PCs don't have a tag

so, question, is this a conversation that the PC has with itself? and, er, what is stopping whatever killed the PC in the first place from killing the PC again, during the dialog...?

  • up
    50%
  • down
    50%
andgalf

Hmmm, you've got some interesting points there for sure.

I'll get rid of the if (!GetIsPC(oPC)) return; line and see what happens.

I tried first with a tag of one of the companions for the sSpeakerTag but as I wanted it to, just as you said, to be a conversation the PC has with hm/herself I left it empty.

"What is stopping whatever killed..." well, that is a good question. The attackers that are fiends and devlis don't attack when the short conversation is fired, at least not ever during my tests, but maybe they would do that if one is unlucky?

The conversation the PC has with him/herself is just a short line of "Just when you are about to die you are transported.." or something along that line, and then in the next node of the conversation the PC and the party is teleported to another area.

  • up
    50%
  • down
    50%
andgalf

Actually, getting rid of that line solved it, it seems. If I'm controlling one of the companions the conversation will come up and have a close up of that character, and not the PC, but that doesn't really matter. The important thing is that the conversation fires and you get teleported. And the conversation fires only when the PC dies it seems.

In all my tests the conversation has never been interupted or corrupted by the fiends attacking when you're dead.

  • up
    50%
  • down
    50%
andysks
Isn't a conversation hiding all hostiles when fired? I could be wrong but I thought it does...
  • up
    50%
  • down
    50%
Clangeddin

As someone who runs Jeg's training module a lot, I can tell that hostiles remain when you are in convos and they keep attacking you. I dont remember if it interrupts the convo, but I wouldnt take my chances with it.

100% sure with nwn1 style convos.

  • up
    50%
  • down
    50%
andysks
It's been so long since I moded the basic mechanics for my campaign that I don't remember if I actually scripted it myself so that hostiles are hidden when in convo. Could be, since you say it's not the default behavior ☺
  • up
    50%
  • down
    50%
kevL's

let's go back to the instant teleport:


// 'ag_mod_death'
/*
    module OnPlayerDeath script
    fires when an OwnedPC dies.
*/

#include "ginc_death"

const string sTAG_DEST = "blessed_wp";

void jump(object oPlayerPc)
{
    object oWp = GetObjectByTag(sTAG_DEST);
    if (GetIsObjectValid(oWp))
    {
        object oParty = GetFirstFactionMember(oPlayerPc, FALSE);
        while (GetIsObjectValid(oParty))
        {
            AssignCommand(oParty, ClearAllActions(TRUE));
            AssignCommand(oParty, JumpToObject(oWp));

            oParty = GetNextFactionMember(oPlayerPc, FALSE);
        }
    }
    else
        SendMessageToPC(GetFirstPC(FALSE), "<c=red>ERROR :</c> ag_mod_death : destination wp is invalid");
}


void main()
{
    object oPlayerPc = GetLastPlayerDied();

    if (!GetIsOwnedByPlayer(oPlayerPc)) // safety.
        return;

    int iJournal = GetJournalEntry("meegan_quest", oPlayerPc);

    // IMPORTANT: this should spec. an exact journal entry (or range of entries)
    // that does not include the completion-id of the quest. And the quest-state
    // should be advanced past that range ASAP. reason: you want the regular
    // death code to be re-enabled ASAP.

    if (iJournal > 10)
    {
        effect eRez = EffectResurrection();

        object oParty = GetFirstFactionMember(oPlayerPc, FALSE);
        while (GetIsObjectValid(oParty))
        {
            if (GetIsDead(oParty)) // only the living can be jumped
                ApplyEffectToObject(DURATION_TYPE_INSTANT, eRez, oParty);

            oParty = GetNextFactionMember(oPlayerPc, FALSE);
        }

        AssignCommand(GetModule(), DelayCommand(0.f, jump(oPlayerPc)));
    }
    else // regular death
    {
        string sScript = GetLocalString(oPlayerPc, "DeathScript");
        if (sScript != "")
            ExecuteScript(sScript, oPlayerPc);

        AssignCommand(oPlayerPc, KnockOutCreature(oPlayerPc));
    }
}



It (or a close version of it) worked for me in a simple, single-area test. But there are other issues, in particular the state of the quest (see note). It might well be a good idea to also check the Player's current area against a specific area-tag ...

  • up
    50%
  • down
    50%
andgalf

Thanks for all the replies. I'm quite fond of the way it is now with the conversation, so I don't know if I'll change it. I will still check your script kevL's out of curiosity.

As it is now, before I'm trying out your script, the journal entry advances through a conversation that takes place almost right after the teleporting, so I guess that should make the regular death code be re-enabled. But I guess I should check that.

  • up
    50%
  • down
    50%
kevL's
  • the journal entry advances through a conversation that takes place almost right after the teleporting

that's good, you should at least change this:

if (nInt > 10)

to

if (nInt == EXACT_QUEST_STATE)


if you know what i mean ... where EXACT_QUEST_STATE is the exact nInt of the journal required for this "free death".

  • up
    50%
  • down
    50%
andgalf

Yep. You're totally right. I've changed that. I don't know why I didn't see that right away.

I tried your script and it works brilliantly (even if at one time for some strange reason NWN2 crashed or just got stuck when loading, but I think that was something temporary). However, since I got used to the short "death conversation" instead of the teleporting-right-away-solution I would want to use the IP-speaker.

A bug with my script that I noticed after testing a bit more is this: If it's one of the companions you're controlling when the IP-speaker is fired,  then after the whole party is transported and you move your characters, that particular character you controlled when the IP-speaker conversation fired and your PC died, does not move with the others. I tried sending out the "Follow me" command, but the character wont move even then. The only sollution to this is to click on the character and move it a bit, and after that if you send the "Follow me" command, the character moves and everything is back to normal. This didn't happen with kevL's script. Is there away around this particular bug? Or could one modify kevL's script to incorporate the conversation? I tried inocorporating the IP-speaker thing in kevL's script but that gave the same bug....so maybe it has to do with IP-speaker?

  • up
    50%
  • down
    50%
andgalf

Instead of kevL's line:

AssignCommand(GetModule(), DelayCommand(0.f, jump(oPlayerPc)));

I tried with ActionStartConversation but that didn't work at all, and maybe I've should have known that. I also tested with things like this:

// 'ag_mod_death'
/*
    module OnPlayerDeath script
    fires when an OwnedPC dies.
*/

#include "ginc_ipspeaker"
#include "ginc_death"

const string sTAG_DEST = "blessed_wp";

void jump(object oPlayerPc)
{
    object oWp = GetObjectByTag(sTAG_DEST);
    if (GetIsObjectValid(oWp))
    {
        object oParty = GetFirstFactionMember(oPlayerPc, FALSE);
        while (GetIsObjectValid(oParty))
        {
            AssignCommand(oParty, ClearAllActions(TRUE));
            AssignCommand(oParty, JumpToObject(oWp));

            oParty = GetNextFactionMember(oPlayerPc, FALSE);
        }
    }
    else
        SendMessageToPC(GetFirstPC(FALSE), "<c=red>ERROR :</c> ag_mod_death : destination wp is invalid");
}


void main()
{
    object oPlayerPc = GetLastPlayerDied();

    if (!GetIsOwnedByPlayer(oPlayerPc)) // safety.
        return;

    int iJournal = GetJournalEntry("meegan_quest", oPlayerPc);

    // IMPORTANT: this should spec. an exact journal entry (or range of entries)
    // that does not include the completion-id of the quest. And the quest-state
    // should be advanced past that range ASAP. reason: you want the regular
    // death code to be re-enabled ASAP.

    if (iJournal == 11)
    {
        effect eRez = EffectResurrection();

        object oParty = GetFirstFactionMember(oPlayerPc, FALSE);
        while (GetIsObjectValid(oParty))
        {
            if (GetIsDead(oParty)) // only the living can be jumped
                ApplyEffectToObject(DURATION_TYPE_INSTANT, eRez, oParty);

            oParty = GetNextFactionMember(oPlayerPc, FALSE);
        }

      // I changed things here
     object oPC = GetFirstPC(FALSE);
      CreateIPSpeaker("", "death_convo", GetLocation(GetFirstPC()), 1.0f);
     // to here
    }
    else // regular death
    {
        string sScript = GetLocalString(oPlayerPc, "DeathScript");
        if (sScript != "")
            ExecuteScript(sScript, oPlayerPc);

        AssignCommand(oPlayerPc, KnockOutCreature(oPlayerPc));
    }
}

And even if this worked I got the same bug I talked about in my last post.

  • up
    50%
  • down
    50%
andgalf

And now I've noticed another little weird bug. At least I think it is a bug. After dying, when using my original script that is, there is some spell cast on all the characters called "regenerate" or something like that, making them regenerate health at a slow rate. It doesn't affect stuff that much but it is really weird. I will continue testing...

  • up
    50%
  • down
    50%
andgalf

I tested with kevL's original script again and none of these two bugs appears when using his script. So I think I'll settle with that. Better to not create unnecessary bugs. Even if I right now like the way that there's a short line of conversation explaining to the player that he and his companions are mortally wounded and teleported, contrary to what I thought was best for the module at first, the player still gets an explanation to what's happening in the next area even without that short line.

So, as I said, I think I'll use kevL's bug-less script.

 

  • up
    50%
  • down
    50%
kevL's
  • there's a short line of conversation explaining to the player that he and his companions are mortally wounded and teleported

ondeath:
- pause
- show notice window

oncallback:
- rez
- teleport to dest.


That, is what should/could be done, rather than invoking a risky dialog. But let's leave it for now ... i need to get back to ... restructuring the dependency hierarchy.

  • up
    50%
  • down
    50%
Clangeddin

If you are still interested, I made an alternative that -should- (I did not test it, to be honest) work a bit better in multiplayer and check for a party "wipe" (all owned characters are dead). It only takes care of the teleport, it will not automatically start the convo or spawn the npc to do the convo with, but that can be added as well.

It will, however, automatically advance the quest for the players who have it on that state (from 11 to 12), so you won't have to worry about that anymore once it fires properly.

#include "ginc_death"

const string DEATH_QUEST = "meegan_quest";

int PartyReviveAndJump(object oPC)
{
    object oWP = GetObjectByTag("blessed_wp");
    if (oWP == OBJECT_INVALID)
    {
        SendMessageToPC(GetFirstPC(FALSE), "<c=red>ERROR :</c> ag_mod_death : destination waypoint is invalid.");
        return FALSE;
    }
    effect eRESS = EffectResurrection();
    object oPARTY = GetFirstFactionMember(oPC, FALSE);
    while (oPARTY != OBJECT_INVALID)
    {
        if (GetIsDead(oPARTY) == TRUE) ApplyEffectToObject(DURATION_TYPE_INSTANT, eRESS, oPARTY);
        if (GetJournalEntry(DEATH_QUEST, oPARTY) == 11) AddJournalQuestEntry(DEATH_QUEST, 12, oPARTY, FALSE);
        oPARTY = GetNextFactionMember(oPC, FALSE);
    }
    DelayCommand(0.0f, JumpPartyToArea(oPC, oWP));
    return TRUE;
}

void main()
{
    int nQUEST;
    object oPC = GetLastPlayerDied();
    object oPARTY = GetFirstFactionMember(oPC, FALSE);
    while (oPARTY != OBJECT_INVALID)
    {
        if (GetJournalEntry(DEATH_QUEST, oPARTY) == 11)
        {
            //At least one party member has this quest, so the whole party has to be flagged for teleport after wipe.
            nQUEST = TRUE;
            break;
        }
        oPARTY = GetNextFactionMember(oPC, FALSE);
    }
    
    if (nQUEST == TRUE)
    {
        int nOWN;
        oPARTY = GetFirstFactionMember(oPC, FALSE);
        while (oPARTY != OBJECT_INVALID)
        {
            //all party members must be dead for the teleport to trigger.
            if (GetIsOwnedByPlayer(oPARTY) == TRUE)
            {
                //If there is at least one player character still alive, we break the loop.
                if (GetIsDead(oPARTY) == FALSE) break;
                nOWN = nOWN + 1;
            }
            oPARTY = GetNextFactionMember(oPC, FALSE);
        }
        //If the loop is not broken, then all owned characters must be dead.
        if ((nOWN > 0) && (oPARTY == OBJECT_INVALID))
        {
            if (PartyReviveAndJump(oPC) == TRUE) return;
        }
    }
    
    // Check for additional death script
    string sSCRIPT = GetLocalString(oPC, "DeathScript");
    if (sSCRIPT != "") ExecuteScript(sSCRIPT, oPC);
    KnockOutCreature(oPC);
}

  • up
    50%
  • down
    50%
andgalf

Thank you for your reply, Clangeddin!

My module happens to be a single player module only, so I'm afraid I don't have any need for the script.

The only thing that would be neat, would be if there was some way to incorparate the short conversation before the teleport, but as you know, if you've read all previous posts, that produces a two minor bugs that I don't want to have in my mod.

  • up
    50%
  • down
    50%
kevL's

i just came across this little chunk of code and it occurred to me that, to use CreateIPSpeaker() you're supposed to specify an arbitrary (unique) tag-string for sSpeakerTag. The function will create an Ipoint placeable at location, that controls the conversation (ie, will be the speaker of the conversation) and automatically labels it with the given tag:

    string sSpeakerTag = "3090_ip_conversations"; // this becomes the tag of the IPSpeaker object.
    string sConversation = "3090_first_drag_dead";
    location lLoc = GetLocation(OBJECT_SELF);
    float fDelay = 2.f;

    CreateIPSpeaker(sSpeakerTag, sConversation, lLoc, fDelay);




Because that empty string for sSpeakerTag really bothered me, not saying this will fix those bugs tho - but for future reference, that's sSpeakerTag

see below

  • up
    50%
  • down
    50%
andgalf

Ok. I tried this but nothing happened. No dialog was fired. Here is the two scripts that I tried (I renamed my death_convo conversation to 3090_first_drag_dead, and I also tried with not renaming letting the 3090_first_drag_dead be as it is, although that conversation doesn't exist to my knowledge, but maybe there are several things I don't understand here):

// 'ag_mod_death'
/*
    module OnPlayerDeath script
    fires when an OwnedPC dies.
*/

#include "ginc_ipspeaker"
#include "ginc_death"

const string sTAG_DEST = "blessed_wp";

void jump(object oPlayerPc)
{
    object oWp = GetObjectByTag(sTAG_DEST);
    if (GetIsObjectValid(oWp))
    {
        object oParty = GetFirstFactionMember(oPlayerPc, FALSE);
        while (GetIsObjectValid(oParty))
        {
            AssignCommand(oParty, ClearAllActions(TRUE));
            AssignCommand(oParty, JumpToObject(oWp));

            oParty = GetNextFactionMember(oPlayerPc, FALSE);
        }
    }
    else
        SendMessageToPC(GetFirstPC(FALSE), "<c=red>ERROR :</c> ag_mod_death : destination wp is invalid");
}


void main()
{
    object oPlayerPc = GetLastPlayerDied();

    if (!GetIsOwnedByPlayer(oPlayerPc)) // safety.
        return;

    int iJournal = GetJournalEntry("meegan_quest", oPlayerPc);

    // IMPORTANT: this should spec. an exact journal entry (or range of entries)
    // that does not include the completion-id of the quest. And the quest-state
    // should be advanced past that range ASAP. reason: you want the regular
    // death code to be re-enabled ASAP.

    if (iJournal == 11)
    {
        effect eRez = EffectResurrection();

        object oParty = GetFirstFactionMember(oPlayerPc, FALSE);
        while (GetIsObjectValid(oParty))
        {
            if (GetIsDead(oParty)) // only the living can be jumped
                ApplyEffectToObject(DURATION_TYPE_INSTANT, eRez, oParty);

            oParty = GetNextFactionMember(oPlayerPc, FALSE);
        }

       
         string sSpeakerTag = "3090_ip_conversations"; // this becomes the tag of the IPSpeaker object.
        string sConversation = "3090_first_drag_dead";
        location lLoc = GetLocation(OBJECT_SELF);
        float fDelay = 2.f;

        CreateIPSpeaker(sSpeakerTag, sConversation, lLoc, fDelay);
    }
    else // regular death
    {
        string sScript = GetLocalString(oPlayerPc, "DeathScript");
        if (sScript != "")
            ExecuteScript(sScript, oPlayerPc);

        AssignCommand(oPlayerPc, KnockOutCreature(oPlayerPc));
    }
}

 

And I tried this code:

 

#include "ginc_ipspeaker"
#include "ginc_death"
#include "ginc_debug"

 
void main()
{
 
 
      object oPC = GetLastPlayerDied();

    int nInt;
    nInt=GetLocalInt(oPC, "NW_JOURNAL_ENTRYmeegan_quest");

    if (nInt == 11)
   
      {
    
         string sSpeakerTag = "3090_ip_conversations"; // this becomes the tag of the IPSpeaker object.
        string sConversation = "3090_first_drag_dead";
        location lLoc = GetLocation(OBJECT_SELF);
        float fDelay = 2.f;

        CreateIPSpeaker(sSpeakerTag, sConversation, lLoc, fDelay);
      }
 
      else
     {
     object oDead = GetLastPlayerDied();
      PrintString( "k_mod_player_death: " + GetName(oDead) + " executing OnPlayerDeath event" );

    // Abort if dying character is not an owned PC
    if ( GetIsOwnedByPlayer( oDead ) == FALSE )
        {
        PrintString( "** k_mod_player_death: " + GetName(oDead) + " not owned by a player. ABORT!" );
        return;    
        }
    
    // Check for additional death script
    string sDeathScript = GetLocalString( oDead, "DeathScript" );
    if ( sDeathScript != "" ) ExecuteScript( sDeathScript, oDead );    
    
    // Queue knockout script
    AssignCommand( oDead, KnockOutCreature( oDead ) );
    }
}

 

Maybe there's something I'm not getting. Maybe I'm supposed to change the tag 3090_ip_conversations to perhaps my_ip_conversations or something? Or is it important with the 3090 in the beginning perhaps?

  • up
    50%
  • down
    50%
kevL's

Sorry i misrepresented sSpeakerTag in CreateIPSpeaker(). It's not the tag of the IPoint, it is the tag of the creature who's going to speak.



in any case, all I'm getting is a barkstring (floating text) when I try to start a self-dialog. This works okay with a companion-speaker, though:

    SetOwnersControlledCompanion(GetControlledCharacter(oPlayerPc));
    AssignCommand(GetModule(), dialog(oPlayerPc));


and this is the subfunction, that replaces jump()

void dialog(object oPlayerPc)
{
    CreateIPSpeaker("kg_ct_tiera", "kg_cv_tiera", GetLocation(oPlayerPc));
}



NOTE: "kg_ct_tiera" is the tag of my companion. "kg_cv_tiera" is her dialog file. replace each as you see fit.


There's just too many potential hazards; floating text, active demons, no tag ...

  • up
    50%
  • down
    50%
andgalf

OK. If all this does is create a barkstring (floating text above the character), I think I'll skip it and use your original script without the dialog instead.

  • up
    50%
  • down
    50%
andysks

So say I have a creature tagged "kevl" and I call CreateIPSpeaker("kevl", ... ), will that creature not be the conversation owner? Will the function create the ip_speaker placeable and give it the tag "kevl"? Or did I not understand what you said there? Because if that's the case, then why have the conversation given to the creature to begin with? Or does it assign an already existing tag with the ip_speaker, making it essentially a copy of it?

  • up
    50%
  • down
    50%
kevL's

hey Andy,
I'm not a CreateIPSpeaker guru. (some people are) But just by staring at the code this is what i gather:

this is ultimately the call that will start the dialog:

    ActionStartConversation(oPC, sConversation, FALSE, FALSE, TRUE, FALSE);

'sConversation' appears to be the same 'sConversation' that is passed into

    CreateIPSpeaker(string sSpeakerTag, string sConversation, location lLoc, float fDelay = 0.f);

(note: I may have changed the variable-names ...)

So if 'sConversation' is a blank string, the dialog that's been assigned to the creature w/ 'sSpeakerTag' will be used. Alternately, a standalone dialog can be invoked instead.


And it looks like the speaker w/ 'sSpeakerTag' will indeed be the conversation owner. That is, the heartbeat runs on the IPoint placeable ( which is created on-the-fly from a stock resref ); but the call for ActionStartConversation() is assigned to 'oSpeaker'. as in:

    AssignCommand(oSpeaker, ActionStartConversation(oPC, sConversation, FALSE, FALSE, TRUE, FALSE));


The tag of the Ipoint will be the string of the dialog prefixed w/ "IPS_"


'oPC' will be:

    oPC = GetFactionLeader(GetFirstPC());

[unless a global-string value is set for var "NX1_S_CUSTOM_IPSPEAKER" - if it is, then the routine will execute a script of that name that should set a creature as a local object on the module (using the same variable but now for an object-value) that will replace the default oPC]

  • up
    50%
  • down
    50%
andysks

For a moment there I thought the ip_speaker placeable that is created on the fly was a virtual copy of the conversation owner that takes over the dialog ownership. I nearly had nightmares. What I don't understand is, why is that placeable even created? If owner and conversation are set as parameters, then what do we need the placeable creation for? There was a time I knew more about the subject but once I made it work, my brain made room for new information. Tchos took the issue to next levels of undestanding but he doesn't post here anymore.

  • up
    50%
  • down
    50%
kevL's

if you look at the stock blueprints in the toolset, filter Placeables w/ "ipspeaker" and the Ipoint Speaker should show in the list.

have a look at its scripts. It has 'gp_ipspeaker_hb' as its heartbeat. That script is what keeps checking if the dialog is ready to start


there are other ways to skin the proverbial horse, but this is as good as any. Why not use a pseudo-heartbeat function? Because if a game is reloaded it takes might take extra scripting to start it up again; but a placeable will start firing its HB auto (with all necessary variables having been stored on the same placeable). Then, after the dialog starts successfully ~poof~ destroy one object and cleanup is all done.

  • up
    50%
  • down
    50%