Ventriloquism Spell (RESOLVED/DONE)

I am looking at it - but I did edit the variable again above. Also, I am working on a better version of this and may have suggestions for simplifying your OnChat script.

sSaid is supposed to be sSpeak. Edit: or the other way around of course :wink:

Really I think you would benefit a lot from actually trying to understand the scripting instead of just saying you don’t know it… It’s not rocket science. You can make sense of it if you put in a little effort.

3 Likes

Hey Mannast…that would be cool and appreciated. :slight_smile:

I know enough to “fake” it…I normally can see a script…follow the logic…copy a script I see that can do something similar…but when it comes to actually writing it…nope…lol…many times I can blend scripts by trial and error…but many times I can’t.

I don’t mean writing it all from scratch. But just trying to actually understand what each statement is doing. A little comprehension of the basics would make your blending a lot less trial and error and I think be more satisfying to you. Tarot has a few basic tutorials. And the (offline at least? ) lexicon has some as well. If you actually read some of them and try to understand instead of just glossing over it you’d find it less frustrating and actually start to make real sense. Anyway, I’m not trying to pick on you :slight_smile:

Keep at it!

Oh believe me…I’ve tried. I mean I have picked up lots over the years. I’m an ideas guy. Some people are gifted…when it comes to scripting it is very hard for me…don’t know why…but it is. but, I really appreciate those who help me with the scripting. I have so many ideas to do things…I can do the spell icons, create the demo modules, create .erfs, haks, etc…I just need help with two things I cannot do. Scripting and modeling.

Hey meaglyn,

I changed all the instances of sSaid to sSpeak and it compiled and worked…thanks man. BUT…lol…I tested my languages widgets I have in my module. So when I change from common to say “orc” and I do the ventriloquism…I say: “hi, how are you” in orc…with the log I targeted as my target to make the voice come from…and I speak the words in orc, but the log says it in common…lol

I will release this to the vault when mannast adds more to this spell as he mentioned above this is just a skeleton of a spell he will add more to. I’m just happy it at least now works…sort of…in my module for now with a slight glitch…lol

Hey Mannast,

meagln’s solution worked for the script. he said just change the “sSaid” to “sSpeak” in all instances and it will compile and work. One thing down. Look forward to the spell being completed in full. :slight_smile:

Still would be nice if the “@” symbol was hidden when the words are placed in the mouth of the creature or placeable…if possible.

Believe me, I know how it is. It took me 5 years for me to get a basic understanding of scripting. I’m glad I’m actually able to do basic stuff nowadays at least, but my brain was never made for scripting either.
I would suggest though, as others already have pointed out, to try and check out tutorials (for me kevL_s tutorial helped the most) from time to time. Eventually you’ll get a hang of it if you’re persistent. Also, don’t forget to use Lilac’s Script Generator. That tought me a lot too.

1 Like

@Imtherealthing if you are on windows try the downloadable Lexicon (otherwise try the online version). It contains a number of tutorials to get you started. Also see this pinned thread for links to other tutorials -

The Starting Point - module building tutorials for beginners - and where to find them

TR

1 Like

This should work to remove the “@” sign.

    int nVolume = GetPCChatVolume();
    int nSpeak= GetLocalInt(oSpeaker, "SPELL_SPEAK");
    string sSpeak = sInput;

    if (nVolume != TALKVOLUME_SILENT_SHOUT)
    {
        // eat leading whitespace
        while (GetStringLeft(sSaid, 1) == " ")
        {
            sSpeak = GetStringRight(sSaid, GetStringLength(sSaid)-1);
        }

        string sLeadChar = GetStringLeft(sSpeak, 1);
        string s2ndChar = GetStringRight(GetStringLeft(sSpeak, 2), 1);
        if (sLeadChar == "@" && nSpeak!=0)//Preface what target should say
        {
        object oPCCopy = GetLocalObject(oSpeaker, "SPELL_TARGET");
       sSpeak = GetStringRight(sSpeak, GetStringLength(sSpeak)-1);//this should remove the @
        AssignCommand(oPCCopy, SpeakString(sSpeak, TALKVOLUME_TALK));
        SetPCChatMessage("");
       return;
        }

This is just the section of your onchat script that pertains to the ventriliquism

1 Like

After having a little time to test this - I found you have the spell not target non-clickable places - so it only targets objects which are “usable” - placeables, npcs, etc. The option to target a location doesn’t work the way I assumed and we don’t need to create the invisible object.

Another question - do you want npc’s to have a save against this spell, or just a skill check DC if a particular attempt will work? And if the attempt fails, do you want the caster to speak the string?

Mannast…you do with it as you like :slight_smile: I’m good with anything you do bud.

Would be cool (if possible)…to make creatures who are near a “usable” placeable to walk to that placeable if they hear a noise come from it…to simulate investigating the noise. I like the ability to make the words come out of your buddies mouths (other PC’s) and NPCs and objects.

Perfect…just tested it. Works great on NPC’s and placeables that are “useable” and no “@” symbol shows up. Awesome job bud!!

Oh…don’t forget to use “sSpeak” in place of “sSaid”…I had to change that in your script again for it to compile and work as meaglyn pointed out.

Thanks guys.

Let me know when you have done the final and I will post this to the vault. Also for the tlk description…how long does one have before the spell expires?

For duration I have it at 60 seconds per caster level. This may seem a bit long, but it takes time to type things.

1 Like

Ok thanks. Perfect

Any further progress during the holidays :slight_smile:

I was just going to upload this for you to check out. There are some other ideas I have for it, but it works. I took the liberty of editing the spells.2da to allow the spell to target everything but the caster. It is in the hak I used. Let me know if you can open this up and your thoughts!
Vent test.7z (38.6 KB)

I will upload the spell and your edited OnChat script here (since it is not the one in my sample mod)
OnChat script

//Main OnPlayerChat event script.
#include "pjr_chat_inc"
#include "x3_inc_horse"
#include "x2_inc_itemprop"
void main()
{

    //Variables
    object oSpeaker=GetPCChatSpeaker();
    string sInput = GetPCChatMessage();
    string sInputL4 = GetStringLeft(sInput, 4);
    int bLanguageOn=GetLocalInt(oSpeaker,"pr_speaking");
    string sText=GetPCChatMessage();
    string sTag;
    object oArea=GetArea(oSpeaker);
    string sName=GetName(oSpeaker);
    int iListenerLanguage;
    // Book authoring section. by thales Darkshine add in

////////Ventriloquism Bits
    object oPC = oSpeaker;
    int nVolume = GetPCChatVolume();
    int nSpeak= GetLocalInt(oPC, "SPELL_SPEAK");
    string sSaid = GetPCChatMessage();
    int nDC = 10;//Base DC of 10

    if (nVolume != TALKVOLUME_SILENT_SHOUT)
    {
        // eat leading whitespace
        while (GetStringLeft(sSaid, 1) == " ")
        {
            sSaid = GetStringRight(sSaid, GetStringLength(sSaid)-1);
        }

        string sLeadChar = GetStringLeft(sSaid, 1);
        string s2ndChar = GetStringRight(GetStringLeft(sSaid, 2), 1);
        if (sLeadChar == "@" && nSpeak!=0)//Preface what target should say
        {
        object oVentri = GetLocalObject(oPC, "VENT_SPELL_TARGET");
        string sName = GetName(oVentri);
        if(GetObjectType(oVentri) == OBJECT_TYPE_WAYPOINT)//If we somehow get the spell to target just a location
            {//we will create a waypoint for the target.  Waypoint's can't talk, so we create a speaker who lasts 1/2 a second
                float fOff = IntToFloat(d20(1))/4;//create a random distance up to 5 units away.
                location lLocal = GenerateNewLocation(oVentri, fOff, 0.0, 0.0);
                oVentri = CreateObject(OBJECT_TYPE_CREATURE, "staff_end",lLocal, TRUE,"Vent_Target");
                SetName(oVentri, sName);
                DestroyObject(oVentri, 0.5);//Give it time to speak
            }
        sSaid = GetStringRight(sSaid, GetStringLength(sSaid)-1);//this should remove the @
        int nCasterLevel = GetCasterLevel(oPC);
        int nCheck =d20(1)+nCasterLevel;
        SendMessageToPC(oPC, "You attempt to throw your voice from the"+sName+".");
        SendMessageToPC(oPC, "DC "+IntToString(nDC)+". You roll "+IntToString(nCheck-nCasterLevel)+
            " + Your Caster Level "+IntToString(nCasterLevel)+".");
        if(nCheck < nDC)
                {//failed to throw voice!
                //option 1 - create a new location and have the voice thrown there
                if(nDC-nCheck<=5)//Only failed by 5 or less
                    {
                    SendMessageToPC(oPC, "You are not quite successful and your voice sounds like it is coming from somewhere else!");
                    float fOff = IntToFloat(d20(1))/4;//create a random distance up to 5 units away.
                    location lLocal = GenerateNewLocation(oVentri, fOff, 0.0, 0.0);
                    oVentri = CreateObject(OBJECT_TYPE_CREATURE, "staff_end",lLocal, TRUE,"Vent_Target");
                    SetName(oVentri, sName);
                    DestroyObject(oVentri, 0.5);//Give it time to speak
                    }
                else
                    {
                    //return;
                //option 2 - have the PC speak the line
                    SendMessageToPC(oPC, "You failed to throw your voice!");
                    SetPCChatMessage(sSaid);
                    return;
                    }
                }
        DelayCommand(0.3, AssignCommand(oVentri, SpeakString(sSaid, TALKVOLUME_TALK)));//Delay allows time for creation of target if needed
        SetPCChatMessage("");
        return;
        }
    }//End of Ventriloquism section
////////////////////////////////////////////////////////////////////////////////
// Hat of Disguise Starter                                                    //
// updated Jan. 15, 2013 - The Amethyst Dragon                                //
// This checks for the start of the HoD commands, and executes the ful HoD    //
// script if oSpeaker is actually wearing the hat.                            //
   if (sInputL4 == "hod ")
      {
      if (GetTag(GetItemInSlot(INVENTORY_SLOT_HEAD, oSpeaker)) == "hod_")
         {
         if (HorseGetIsMounted(oSpeaker) != TRUE)
            {
            SetLocalString(oSpeaker, "hodstring", sInput);
            ExecuteScript("hod_onchat", oSpeaker);
            }
         else
            {
            SendMessageToPC(oSpeaker, "You cannot use the Hat of Disguise while mounted.");
            }
         SetPCChatMessage("");
         return;
         }                                                                    //
      }                                                                       //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// DLA Quiver Code                                                            //
// added Jan. 15, 2013 - The Amethyst Dragon                                  //
if (sInput == "quiver on")
   {
   object oWorkContainer = IPGetIPWorkContainer();
   object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oSpeaker);  // get armor object
   if (oArmor != OBJECT_INVALID)                                   // continue only if armor is equipped
      {
      int nCurrentNeck = GetItemAppearance(oArmor, ITEM_APPR_TYPE_ARMOR_MODEL, ITEM_APPR_ARMOR_MODEL_NECK);
      if (nCurrentNeck != 7)                                       // only add if not already showing quiver
         {
         object oWorking1 = CopyItem(oArmor, oWorkContainer, TRUE);
         DestroyObject(oArmor);
         object oArmor2 = CopyItemAndModify(oWorking1, ITEM_APPR_TYPE_ARMOR_MODEL, ITEM_APPR_ARMOR_MODEL_NECK, 7, TRUE);
         AssignCommand(oSpeaker, ClearAllActions(TRUE));
         AssignCommand(oSpeaker, ActionEquipItem(oArmor2, INVENTORY_SLOT_CHEST));
         DestroyObject(oWorking1);
         SetLocalInt(oArmor2, "original_neck", nCurrentNeck + 1);  // the "+1" is because "0" is a valid choice for armor parts
         SendMessageToPC(oSpeaker, "Quiver added to: " + GetName(oArmor2));
         }
      }
   }
else if (sInput == "quiver off")
   {
   object oWorkContainer = IPGetIPWorkContainer();
   object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oSpeaker);  // get armor object
   if (oArmor != OBJECT_INVALID)                                   // continue only if armor is equipped
      {
      int nCurrentNeck = GetItemAppearance(oArmor, ITEM_APPR_TYPE_ARMOR_MODEL, ITEM_APPR_ARMOR_MODEL_NECK);
      if (nCurrentNeck == 7)                                       // only remove if showing quiver
         {
         int nOriginalNeck = GetLocalInt(oArmor, "original_neck");
         if (nOriginalNeck < 1) { nOriginalNeck = 1; }             // if variable not set, default to 1
         else { nOriginalNeck = nOriginalNeck - 1; }               // back to original value, which could be 0
         object oWorking1 = CopyItem(oArmor, oWorkContainer, TRUE);
         DestroyObject(oArmor);
         object oArmor2 = CopyItemAndModify(oWorking1, ITEM_APPR_TYPE_ARMOR_MODEL, ITEM_APPR_ARMOR_MODEL_NECK, nOriginalNeck, TRUE);
         AssignCommand(oSpeaker, ClearAllActions(TRUE));
         AssignCommand(oSpeaker, ActionEquipItem(oArmor2, INVENTORY_SLOT_CHEST));
         DestroyObject(oWorking1);
         SendMessageToPC(oSpeaker, "Quiver removed from: " + GetName(oArmor2));
         }
      }                                                                       //
   }                                                                          //
////////////////////////////////////////////////////////////////////////////////



    if (GetPCChatVolume() == TALKVOLUME_TALK)
    {
        if ((GetStringLeft(sText, 5)=="name-") || (GetStringLeft(sText, 6)=="write-"))
        // if message was a talk then delet previous string and save new one
        // save last chat on PC.
        DeleteLocalString(oSpeaker,"TD_QUILLCHAT");
        DelayCommand(0.5,SetLocalString(oSpeaker,"TD_QUILLCHAT", sText));
    }
    //Quick return if not speaking a language to try and cut down on lag.
    if (bLanguageOn == FALSE)
    {
        return;
    }
    //Check if this is the DM Channel and educate
    if (GetPCChatVolume()==TALKVOLUME_SILENT_SHOUT)
    {
        SetPCChatVolume(TALKVOLUME_TELL);
        SetPCChatMessage("You do not need to type to the DM channel while using language widgets anymore. Nor do you need to type a bracket in front of your text. Please simply type normally. (If you wanted to contact a DM please switch your language off.)");
        return;
    }

    //Text is a language, get which one the pc is speaking
    int iLanguage=GetLocalInt(oSpeaker,"pr_language");
    string sSpeaking=GetLanguageName(iLanguage);

    //Translate it to their language and format the translation
    string sOutput=TranslateCommonToLanguage(iLanguage,sText);
    string sTranslate=TMESSAGE_TEXT+sName+ " says in: "+sSpeaking+ " : "+sText+COLOR_END;

    //Send a message translating what the speaker says to anyone who can hear.
    object oListener=GetFirstObjectInArea(oArea);    //cycle through area
    while (GetIsObjectValid(oListener))
    {
        if (GetIsPC(oListener))
        {
            if (GetObjectHeard(oSpeaker,oListener))  //did they actually hear the speech?
            {
                if ((GetIsDM(oListener)) || (GetIsDMPossessed(oListener))) //DMs speak every language
                {
                    DelayCommand(0.3,SendMessageToPC(oListener,sTranslate));
                }
                else  //Not a DM - check for a matching language widget
                {
                    if (GetLanguageWidgetMatch(oListener,iLanguage))
                        DelayCommand(0.3,SendMessageToPC(oListener,sTranslate));
                }
            }
        }
        oListener=GetNextObjectInArea(oArea);
    }

    //Play the now-garbled message for everyone else
    SetPCChatMessage(sOutput);
}

Spell Script

//Sample Ventriloquism spell - Mannast Dec 2022
// Spell 2da edited to target everything except self
// Each different target desired requires a seperate casting.
// There is a DC on each attempt to have the target say something
// Which is done in the OnChat script.
#include "x0_i0_spells"
#include "x2_inc_spellhook"

void main()
{
    //--------------------------------------------------------------------------
    /*
      Spellcast Hook Code
      Added 2003-06-20 by Georg
      If you want to make changes to all spells,
      check x2_inc_spellhook.nss to find out more
    */
    //--------------------------------------------------------------------------
    if (!X2PreSpellCastCode())
    {
        return;
    }
    // End of Spell Cast Hook
    //Setup for Spell
    object    oTarget      = GetSpellTargetObject();
    location  lLocal       = GetSpellTargetLocation();
    int       nCasterLevel = GetCasterLevel(OBJECT_SELF);
    float fDelay = IntToFloat(nCasterLevel)*60;

//If somehow the target is not valid, we can create a target and see if it works
    if(oTarget == OBJECT_INVALID )
        {
        oTarget = CreateObject(OBJECT_TYPE_WAYPOINT, "nw_waypoint001",lLocal, TRUE,"Vent_Target");
        SetName(oTarget, "Somewhere");//We could try and get a description of the area near the target and use it in the name
        DestroyObject(oTarget, fDelay);
        }

    //Set variables for OnChat to use
    SetLocalObject(OBJECT_SELF, "VENT_SPELL_TARGET", oTarget);
    SetLocalInt(OBJECT_SELF, "SPELL_SPEAK", 1);
    SendMessageToPC(OBJECT_SELF, "You can try to make the target say your words.  Type @ before what you would like them to say.");

    //Clean up after spell duration ends
    DelayCommand(fDelay, DeleteLocalObject(OBJECT_SELF, "VENT_SPELL_TARGET"));
    DelayCommand(fDelay, DeleteLocalInt(OBJECT_SELF, "SPELL_SPEAK"));
    DelayCommand(fDelay, SendMessageToPC(OBJECT_SELF, "Your Ventriloquism Spell has expired"));
}
  • notes:
    If you cast a new version of the spell while the previous is still active, it is still under the duration of the previous casting. This is fixable, I just haven’t done it yet

I have noticed every once in a while the words don’t happen - This is likely a timing issue, but could be related to the random location being invalid. Still working on it.

1 Like

My test…after I dropped a level up gal in there to be able to level up to get the spell…

I rolled on a DC that suceeded with throwing my voice at the NPC. It worked as it should have.

On the second spell I cast…I picked the placeable brazier. I cast the spell. I rolled a 3 + 3 (my level) = 6 on a DC of 10. It says I failed, yet the brazier still said the words…

I like the saving throw :slight_smile:

1 Like

I posted it to the vault…gave you credit too. Thanks bud

Also thanks to others on here too for your help and input.

If you want to make further changes to the spell…let me know and I will update the vault entry.

1 Like