Skip to content

My last 2 script to "end" the module [HELP]

MorganZMorganZ Member Posts: 17
Hello, I've almost finished my module (with a lot of help from the community members of this forum! special thanks to ForSerious).

Now I'am missing just a couple of scripts!

The first one is a persistance script to record just the position and the hp of charcaters, I've tried this one I've found on the vault with no luck: https://neverwintervault.org/project/nwn1/script/easy-persistent-locations

The second one is a script to destroy a placeble conteiners if a player places specific item in it (I want player to be able to destroy a rock and open a passage revealing a transition if they craft a specific item and then "use it" on the rock).


Thanks for reading, hope you can help me!

Comments

  • ForSeriousForSerious Member Posts: 466
    Thanks.

    I have scripts for the location and HP. (The location doesn't always work though. I know how to make it work, just haven't done it yet.)

    For the second thing, it sounds conflicting. Are they going to use the crafted item on the rock, or place the item in the inventory of the rock? Both are possible.
  • ABJECT_SELFABJECT_SELF Member Posts: 24
    MorganZ wrote: »
    The second one is a script to destroy a placeble conteiners if a player places specific item in it (I want player to be able to destroy a rock and open a passage revealing a transition if they craft a specific item and then "use it" on the rock).

    I agree that this needs some clarification but it sounds entirely doable.
    Does this pseudocode roughly sound like what you're going for?
    if (LastItemPlacedInContainer == KeyItem){
    	DestroyContainer;
    	DestroyRock;
    }
    
  • MorganZMorganZ Member Posts: 17
    I was thinkin that placing the item in the cointainer would have been easier, but if it is better to "use it" on the container it's fine! So.. yes i think something like that should be perfect. What i want to achieve is just letting players destroy the rocks with explosives. If they have to "use unique power" or place the explosives in a rock used as container it's the same for me!

    thanks!
  • FreshLemonBunFreshLemonBun Member Posts: 909
    You can make a whole thing where they set charges that counts down and explodes, destroying anything with a certain tag nearby.
  • ForSeriousForSerious Member Posts: 466
    edited June 2021
    Alrighty. Starting with saving HP levels as players leave.
    You need a way to have unique identifiers for players. I like to use the tag of the player. By default it's empty.
    Here's the include script to make unique tags for players:
    // The main database name
    const string DB = "datum";
    // The PlayerHP prefix
    const string HP = "hp";
    // The Tag index
    const string TAG = "tag";
    // Makes a unique tag to be assigned on a player.
    string MakePlayerTag(object oPC);
    string MakePlayerTag(object oPC)
    {
        // Pull the last number that was used to make a tag from the database.
        int iTag = GetCampaignInt(DB, TAG);
        // Add one to it. That makes it unique.
        iTag = iTag + 1;
        // Store that in the database as the last number used.
        SetCampaignInt(DB, TAG, iTag);
        // Convert the integer into a string for manipulation.
        string sParsed = IntToString(iTag);
        int iLen;
        // Add a bunch of zeros to the front of the number.
        // The goal here is to have a unique tag for each player that is always the same length.
        for(iLen = GetStringLength(sParsed); iLen <= 6; iLen++)
        {
            sParsed = "0" + sParsed;
        }
        // Add a unique prefix. This can be used to check if a player somehow (Hacking?) added a tag to their character.
        sParsed = "ZZ" + sParsed;
        // Set the unique tag to the player object.
        SetTag(oPC, sParsed);
        // Return the tag for use in the script that calls this function.
        return sParsed;
    }
    
    This script will be called database_stuff in all the other scripts.

    Next we have the OnExit script. Though you can use this in the area OnExit, don't. Put it in the module OnExit script.
    //That first script
    #include "database_stuff"
    //Used to save player HP without temporary HP.
    void TrySavingHPWithoutTempHP(object oPC, string sName);
    void main()
    {
        // The player that is leaving the game.
        object oPC = GetExitingObject();
        // The area where the player left.
        object oArea = GetArea(oPC);
        // That unique tag.
        string sName = GetTag(oPC);
        // HP at time of leaving.
        int iHP = GetCurrentHitPoints(oPC);
        // The x y and z values of the location the player left from.
        vector v = GetPosition(oPC);
        // The value of the ratation that player was at.
        float o = GetFacing(oPC);
        float x = v.x;
        float y = v.y;
        float z = v.z;
        // You can skip this if you don't care. Without it, players can heal themselves with temperary HP spells and abilities.
        // Check if the player has temp HP.
        if(GetHasEffect(EFFECT_TYPE_TEMPORARY_HITPOINTS, oPC) == TRUE)
        {
            // Make a copy of the player object because the original will become OBJECT_INVALID before this runs all the way.
            // You need to make a waypoint with the tag "The_Point" in some area that players cannot access.
            object oCopy = CopyObject(oPC, GetLocation(GetObjectByTag("The_Point")));
            // Make a new temp HP effect.
            effect eTempAdd = EffectTemporaryHitpoints(1);
            // Apply it to the copy object. This will overright the effect the player had.
            ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eTempAdd, oCopy, 5.0f);
            // After that effect wears off, save the HP value.
            DelayCommand(6.2, TrySavingHPWithoutTempHP(oCopy, sName));
        }
        //If the player leaves the game at 0 HP, when they come back, it looks like they have never had their HP saved before.
        if(iHP == 0)
        {
            // I do have things changed so that player death doesn't trigger until -10 HP. Default is 0.
            // So this may not matter.
            iHP = -1;
        }
        // The the HP value to the database.
        SetCampaignInt(DB, HP + sName, iHP);
        WriteTimestampedLogEntry("Player left: " + sName);
        // Let us save the player location while we're here.
        // This is an exception for the starting area. No reason to save. You can add more areas players should not be able to save location in.
        if(GetTag(oArea) != "StartAreaTag")
        {
            // Save all the details individually
            SetCampaignFloat(DB, "LOC_X_" + sName, x);
            SetCampaignFloat(DB, "LOC_Y_" + sName, y);
            SetCampaignFloat(DB, "LOC_Z_" + sName, z);
            // Facing
            SetCampaignFloat(DB, "LOC_O_" + sName, o);
            // Area tag.
            SetCampaignString(DB, "LOC_A_" + sName, GetTag(oArea));
            // This function randomly saves a random placeable object as the area. That's why we're doing it the complecated way.
            //SetCampaignLocation(DB, PLAYER_LOC + sName, lPC);
        }
    }
    void TrySavingHPWithoutTempHP(object oPC, string sName)
    {
        if(GetIsObjectValid(oPC))
        {
            // At this point the temp HP effect has been removed.
            int iHP = GetCurrentHitPoints(oPC);
            WriteTimestampedLogEntry("Leaving HP got reset to: " + IntToString(iHP));
            WriteTimestampedLogEntry(sName);
            SetCampaignInt(DB, HP + sName, iHP);
            // Get rid of the copied player object.
            DestroyObject(oPC);
        }
        else
        {
            WriteTimestampedLogEntry("Object was made invalid by the time this got called.");
        }
    }
    
  • ForSeriousForSerious Member Posts: 466
    Here's the OnEnter script:
    #include "database_stuff"
    void main()
    {
        // Get entering player
        object oPC = GetEnteringObject();
        // Ignore DMs
        if(GetIsDM(oPC))
        {
            return;
        }
        // Create unique string that can be got after the player logs.
        string sName = GetTag(oPC);
        int iXP = GetXP(oPC);
        // If new...
        // Check if they have no tag. Or if they have a tag that does not start with ZZ
        if(GetStringLength(sName) <= 0 || GetSubString(sName, 0, 2) != "ZZ")
        {
           // Mark that they are no longer new.
           MakePlayerTag(oPC);
           int iGold = GetGold(oPC);
        }
        //get the players saved HP
        int iHP = GetCampaignInt(DB, HP + sName);
        //Set the players HP to its saved value.
        int iHPmax = GetMaxHitPoints(oPC);
        // We need to get how much HP to take away from the PC.
        int iHPold = iHPmax - iHP;
        if (iHP != 0)
        {
            // Take the HP away with devine damage. You can change the type.
           effect eDamage = EffectDamage(iHPold, DAMAGE_TYPE_DIVINE, DAMAGE_POWER_NORMAL);
           ApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oPC);
        }
    }
    
  • ForSeriousForSerious Member Posts: 466
    edited June 2021
    And here's the restore location script:
    #include "database_stuff"
    void main()
    {
        // Basically reverse the process of how the location was saved in the database.
        object oPC = GetPCSpeaker();
        string sName = GetTag(oPC);
        float o = GetCampaignFloat(DB, "LOC_O_" + sName);
        float x = GetCampaignFloat(DB, "LOC_X_" + sName);
        float y = GetCampaignFloat(DB, "LOC_Y_" + sName);
        float z = GetCampaignFloat(DB, "LOC_Z_" + sName);
        string sArea = GetCampaignString(DB, "LOC_A_" + sName);
        vector vLoc = Vector(x, y, z);
        object oRoughArea = GetObjectByTag(sArea);
        location lPC = Location(oRoughArea, vLoc, o);
        if(GetIsObjectValid(oCheck))
        {
            AssignCommand(oPC, ClearAllActions());
            AssignCommand(oPC, JumpToLocation(lPC));
        }
        else
        {
            FloatingTextStringOnCreature("OOPS!", oPC, FALSE);
            SendMessageToPC(oPC, "Sorry dude. You have no saved location.");
        }
    }
    
    I have this set up to use in a conversation. You can change that to how you want.

    Sorry if any of these don't compile right away. I just took them from what I have and edited out all the parts you don't need. I'm also sorry if you thought it was going to be easy.
  • ForSeriousForSerious Member Posts: 466
    This one I did test.
    So the easiest option for the exploding obstacle, is to place an item in the inventory.
    I put this script in the OnClosed slot.
    // Para Bilar La Bomba…
    void main()
    {
        object oItem = GetFirstItemInInventory();
        while(GetIsObjectValid(oItem))
        {
            if(GetTag(oItem) == "CraftedItemTag")
            {
                DestroyObject(oItem);
                DelayCommand(0.5, SpeakString("5"));
                DelayCommand(1.5, SpeakString("4"));
                DelayCommand(2.5, SpeakString("3"));
                DelayCommand(3.5, SpeakString("2"));
                DelayCommand(4.5, SpeakString("1"));
                DelayCommand(0.5, PlaySound("gui_trapsetoff"));
                DelayCommand(1.5, PlaySound("gui_trapsetoff"));
                DelayCommand(2.5, PlaySound("gui_trapsetoff"));
                DelayCommand(3.5, PlaySound("gui_trapsetoff"));
                DelayCommand(4.5, PlaySound("gui_trapsetoff"));
                effect eExplode = EffectVisualEffect(VFX_FNF_FIREBALL);
                effect eExplode1 = EffectVisualEffect(VFX_FNF_ELECTRIC_EXPLOSION);
                DelayCommand(5.5,(ApplyEffectToObject(DURATION_TYPE_INSTANT, eExplode, OBJECT_SELF)));
                DelayCommand(6.0,(ApplyEffectToObject(DURATION_TYPE_INSTANT, eExplode, OBJECT_SELF)));
                DelayCommand(6.5,(ApplyEffectToObject(DURATION_TYPE_INSTANT, eExplode1, OBJECT_SELF)));
                DelayCommand(6.5, DestroyObject(OBJECT_SELF));
                return;
            }
            oItem = GetNextItemInInventory();
        }
    }
    
  • MorganZMorganZ Member Posts: 17
    Wow that's a lot of scripting!! thank you! I will try those monday. Just a question, the persistence saving script will save the character location or the player location? I mean what happens if the player changes characters?
  • ForSeriousForSerious Member Posts: 466
    It saves per character. It can be changed to do it by CDkey. I never though about doing it that way before.
  • MorganZMorganZ Member Posts: 17
    Character saving is better! I was just wondering.. looking forword to try it monday!!
  • MorganZMorganZ Member Posts: 17
    Works perfectly!! I've upped my pw to do some play test with resticted player base of 10 friends and trhu dialogue they can go back to where they left! Thanks :)
  • ForSeriousForSerious Member Posts: 466
    Sounds awesome. Glad I could help.
  • bumankumar3bumankumar3 Member Posts: 1
    edited September 2021
    Post edited by bumankumar3 on
  • GenisysGenisys Member Posts: 37
    edited September 2021
    Sadly, it's not easy to store / retrieve locations into a database, they tend to go wrong from time to time, but the above script looks pretty good. I prefer to use objects to store persistent data... e.g. Chest / Items
  • ForSeriousForSerious Member Posts: 466
    I agree, for the most part. Some things that I have heard of, but not had happen to me though, suggest that DB is the best way to go.
    I've been using Set/GetCampaignLocation for a few months, and quite randomly it saves a random placeable object instead of an area object. My code above, has not had that issue... yet.

    In the server I used to play on most, it was heavily recommended protocol to re-log your character after transferring items to them. The reason being that if the server crashed, you wouldn't lose your gear. It would have saved the character that just lost the gear, but not saved the one that just picked it up. Not sure on the exact timings, but the server only saves characters to disc like once every half hour or so, unless they log off.
    Because of that, I try to save critical things to the database, and lesser things to the PC Properties skin. (Like settings the player can change.)
  • WilliamDracoWilliamDraco Member Posts: 175
    The Set/GetCampaignLocation is a special case.
    A location is made of three things.
    1) The coordinate of the location
    2) The Facing/rotation of the location
    3) The area of the location - saved by objectID.

    The issue comes that ObjectID is not a permanent assignation, but instead simply counts from 0-up every time the module is loaded. The first object (the module) gets objectID0, the next object loaded (the first area) gets 1, next object 2 etc.etc.etc

    This makes it naturally volatile. If any changes are made to the area list, the order in which they load will change, which means different objectIDs, which means the location no longer makes sense.

    Most persistence functions instead save the three components above themselves, usually using the area Tag instead of ObjectID to resolve the abovementioned issue.
  • GenisysGenisys Member Posts: 37
    string MODULENAME = "YourModuleName";
    
    void StoreLocation(object oPC)
    {
     object oArea = GetArea(oPC);
     vector vPos = GetPosition(oPC);
     float fFace = GetFacing(oPC);
     location lPC = Location(oArea, vPos, fFace);
    
     if(GetLocalInt(GetArea(oPC), "NORECALL") >= 1) {
      SendMessageToPC(oPC, "Your location cannot be saved in this area."); return; }
     if(GetIsObjectValid(GetNearestObjectByTag("NORECALL", oPC))){
      SendMessageToPC(oPC, "Your location cannot be saved in this area."); return; }
     if(GetAreaFromLocation(lPC) == OBJECT_INVALID){ //Make sure the PC isn't transitioning!
      SendMessageToPC(oPC, "Your location cannot be saved, the location is NOT Valid!");
      return; }
    
      SetLocalLocation(oPC, "CURRENTLOC", lPC);
      SetCampaignLocation(MODULENAME, "GENPCLOC", lPC, oPC);
      SendMessageToPC(oPC, "Location Saved");
    
    }
    

    That's what I use in my location scripting, works beautifully IF the module hasn't changed, e.g. I've edited the module, and that's where locations suddenly go bad...
Sign In or Register to comment.