Grimlar - Guide To Portal Stones

It is assumed that you have an understanding of the NWN scripting language and are familiar with the Aurora Toolset, i.e. you know how to create an area, place objects in it and perform simple scripting tasks. If this isn't the case, please go and have a look at Celowins scripting tutorials and Bioware's module construction tutorial first. Please also note that this tutorial makes use of Tag based Item scripts and as such may not work if you don't have HoTU installed.


Introduction


Everybody wants an easy life, and walking through empty map segments that you've already been through is nobodies' idea of fun.


So what do you do?


The Stone of Recall is a nice idea but can be used too frequently to be perfect, the Focus crystal stuff in SoU and Ring of the Reaper in HoTU are better but wouldn't it be better still if you could choose both where to start the teleport from and where to end up?


The Teleport Stones do just that allowing you to mark the place you want to return to aswell as where you want to leave from.


This is achieved by having both a Portal Stone and a Beacon Stone. The Beacon stone needs to be used first to mark the place you wish to return to, and the Portal Stone is used later when you wish to create a Portal to travel from one location to another.


Easy.


Quick Design


Ok, the plan is as follows...


You create a Beacon with the Beacon Stone, this is the point you want your player to return to. You create a Portal with the Portal Stone, this is the point you are leaving from.


You use Power Stones to fuel the Portal and Beacon Stones, otherwise the player would be able to use them only so many times per day, assuming you don't want unrestricted use. Charges on the items are a possibility but a maximum of fifty charges leads to virtually unrestricted use again, plus recharging could lead to other issues.


The Portal Stone will consume one Power Stone when it is used and the Beacon Stone consumes two.


Ideally the Portal and the Beacon will look similar but different. Also using two placeables allows us to make it immediately obvious which is the Portal and which the Beacon.


Details


What we need to produce here is three new items, the Beacon and Portal Stones which will be 'Miscellaneous Small' items with the 'Cast Spell : Unique Power, Unlimited Use', property. And a new gem called a 'Power Stone', with no powers.


Then we will need to create two new placeables, one to act as a beacon and the other to act as a portal.


Setting Up


It is suggested that you create a module with two small areas, perhaps 4x4, to run this tutorial in, and then link the two areas either by a door transition or an area transition trigger. The tileset used is entirely up to you.


Power Stone


We'll do this item first as it requires least effort.


1)Start the 'Item Wizard' from the 'Wizards' menu at the top of the page.


2)Select 'Gem' for the 'Item Type' and click on 'Next'.


3)Enter 'Power Stone' as the name of the item and click 'Next'.


4)Indicate the category to store it in, something like 'Special : Custom 1' and click 'Next'.


5) Tick the 'Launch Item Properties' check box and click on 'Finish'.


6)On the 'General' properties page, change 'Stack Size' to 1 and 'Additional Cost' to 100.


7)On the 'Description' page, add the following to the 'Identified Description', "This gem seems to resonate with a strange power."


8)On the 'Appearance' page choose a suitable appearance, something like 'iit_gem_016'.


That's it.


Beacon Stone


1)Start the 'Item Wizard' from the 'Wizards' menu at the top of the page.


2)Select 'Miscellaneous Small' for the 'Item Type' and click on 'Next'.


3)Enter 'Beacon Stone' as the name of the item and click 'Next'.


4)Indicate the category to store it in, something like 'Special : Custom 1' and click 'Next'.


5) Tick the 'Launch Item Properties' check box and click on 'Finish'.


6)On the 'General' properties page, change 'Additional Cost' to 97, make a note of the 'Tag'. In this case 'BeaconStone'.


7)On the 'Description' page, add the following to the 'Identified Description':


"When the Beacon Stone is used a magical marker, or beacon, is placed at the specified location to indicate a place the adventurer would like to return to at some point in the future.
This beacon must be in place before a Portal Stone can be used to create a two way passageway between the beacon and wherever the adventurer is at that point in time.
Once a beacon has been placed, it will remain until the adventurer places another beacon.
The adventurer will need to have at least two Power Stones in order to create a beacon."


8)On the 'Appearance' page choose a suitable appearance, something like 'iit_smlmisc_080'.


9)On the 'Properties' page add the following power, 'Cast Spell : Unique Power [Unlimited Uses / Day]' and tick the 'Identified' box, click on 'OK' to save the property changes.


10)Open the 'Script Editor' from the 'Tools' menu at the top of the page. Type in the following code:


//::///////////////////////////////////////////////
//::Number of Items include file
//:://////////////////////////////////////////////
/*
    This contains a corrected version of the TakeNumItems function
    and a related function GetNumItems originally from the 'nw_i0_plot' include file
*/
//:://////////////////////////////////////////////

// Remove nNumItems Items of Type sItem (Tag) from oTarget
void TakeNumItems(object oTarget,string sItem,int nNumItems);

// Return the number of items oTarget possesses from type sItem (Tag)
int GetNumItems(object oTarget,string sItem);

/*void main(){} /**/

// Return the number of items oTarget possesses from type sItem (Tag)
int GetNumItems(object oTarget,string sItem)
{
    int nNumItems = 0;
    object oItem = GetFirstItemInInventory(oTarget);

    while (GetIsObjectValid(oItem) == TRUE)
    {
        if (GetTag(oItem) == sItem)
        {
            nNumItems = nNumItems + GetNumStackedItems(oItem);
        }
        oItem = GetNextItemInInventory(oTarget);
    }
    return nNumItems;
}

// Remove nNumItems Items of Type sItem (Tag) from oTarget
void TakeNumItems(object oTarget,string sItem,int nNumItems)
{
    int nCount = 0;
    object oItem = GetFirstItemInInventory(oTarget);

    while (GetIsObjectValid(oItem) == TRUE && nCount < nNumItems)
    {
        //Is this the item we are looking for?
        if (GetTag(oItem) == sItem)
        {
            //handle single items
            if ((GetItemStackSize(oItem)==1) && (nCount < nNumItems))
            {
                DestroyObject(oItem);
                nCount++;
            }
            else
            {
                //handle stacks too
                while (GetItemStackSize(oItem)>1 && nCount < nNumItems)
                {
                    SetItemStackSize(oItem,GetItemStackSize(oItem)-1);
                    nCount++;
                }
            }
        }
        oItem = GetNextItemInInventory(oTarget);
    }
   return;
}

Note:


GetNumItems and TakeNumItems are actually official functions defined in 'nw_i0_plot' but unfortunately there is an error in the TakeNumItems function that means it doesn't handle stacks of items properly. Hence the need for the functions to be separated out into the 'tut_numitems' include file.


11)Click on the 'Create A New File' icon at the top of the page, (second from left,) and type in the following code:


//::///////////////////////////////////////////////
//::Beacon Stone Item Event Script
//:://////////////////////////////////////////////
/*
    The item event script brings all of the items
    scripts together in one place.
*/
//:://////////////////////////////////////////////

#include "x2_inc_switches"
#include "tut_numitems"

//Create a beacon given a player character and a location
void CreateBeacon(object oPC, location lTarget);

void main()
{
    int nEvent =GetUserDefinedItemEventNumber();
    object oPC;
    object oItem;

    // * This code runs when the Unique Power property of the item is used
    // * Note that this event fires for PCs only
    if (nEvent ==  X2_ITEM_EVENT_ACTIVATE)
    {

        oPC   = GetItemActivator();
        oItem = GetItemActivated();
        location lTarget = GetItemActivatedTargetLocation();
        CreateBeacon(oPC, lTarget);
    }
}

//Create a beacon given a player character and a location
void CreateBeacon(object oPC, location lTarget)
{
    int nPowerUse = 2;                  //How many power stones required to use
    string sPowerGem = "PowerStone";    //Tag of the power stones

    //Do we have at least nPowerUse power stones
    if (GetNumItems(oPC,sPowerGem)< nPowerUse)
    {
        //do nothing
        return;
    }

    //remove nPowerUse power stones
    TakeNumItems(oPC,sPowerGem,nPowerUse);

    //Clear existing portals if any
    object oPort1 = GetLocalObject(oPC,"Port1");
    if (oPort1 !=OBJECT_INVALID)
    {
        DestroyObject(oPort1);
        DeleteLocalObject(oPC,"Port1");
    }
    object oPort2 = GetLocalObject(oPC,"Port2");
    if (oPort2 !=OBJECT_INVALID)
    {
        DestroyObject(oPort2);
        DeleteLocalObject(oPC,"Port2");
    }

    //Create a beacon object and store its location
    oPort2 = CreateObject(OBJECT_TYPE_PLACEABLE,"beaconmarker",lTarget);

    if (!(oPort2==OBJECT_INVALID))
    {
        //if this portal is up, assign values
        SetLocalObject(oPC,"Port2",oPort2);
        SetLocalObject(oPort2,"Creator",oPC);
    }

}

Give the file the same name as the 'Tag' of the item (Step 6), ie 'beaconstone', and compile it.


That's it.


Portal Stone


1)Start the 'Item Wizard' from the 'Wizards' menu at the top of the page.


2)Select 'Miscellaneous Small' for the 'Item Type' and click on 'Next'.


3)Enter 'Portal Stone' as the name of the item and click 'Next'.


4)Indicate the category to store it in, something like 'Special : Custom 1' and click 'Next'.


5) Tick the 'Launch Item Properties' check box and click on 'Finish'.


6)On the 'General' properties page, change 'Additional Cost' to 97, make a note of the 'Tag'. In this case 'PortalStone'.


7)On the 'Description' page, add the following to the 'Identified Description':


"When the Portal Stone is used, a two way passageway is created between the indicated location and a beacon created earlier with a Beacon Stone.
Once a portal has been created it will last until the adventurer travels back to the portal entrance from the beacon.
The adventurer will need to have at least one Power Stone in order to create a portal."


8)On the 'Appearance' page choose a suitable appearance, something like 'iit_smlmisc_079'.


9)On the 'Properties' page add the following power, 'Cast Spell : Unique Power [Unlimited Uses / Day]' and tick the 'Identified' box, click on 'OK' to save the property changes.


10)Open the 'Script Editor' from the 'Tools' menu at the top of the page. Type in the following code:


//::///////////////////////////////////////////////
//::Portal Stone Item Event Script
//:://////////////////////////////////////////////
/*
    The item event script brings all of the item
    scripts together in one place.
*/
//:://////////////////////////////////////////////

#include "x2_inc_switches"
#include "tut_numitems"

//Create a portal given a player character and a location
void CreatePortal(object oPC, location lTarget);

void main()
{
    int nEvent =GetUserDefinedItemEventNumber();
    object oPC;
    object oItem;

    // * This code runs when the Unique Power property of the item is used
    // * Note that this event fires for PCs only
    if (nEvent ==  X2_ITEM_EVENT_ACTIVATE)
    {

        oPC   = GetItemActivator();
        location lTarget = GetItemActivatedTargetLocation();
        CreatePortal(oPC, lTarget);
    }
}

void CreatePortal(object oPC, location lTarget)
{
    int nPowerUse = 1;                  //How many power stones required to use
    string sPowerGem = "PowerStone";    //Tag of power stone

    //Do we have at least nPowerUse power stone
    if (GetNumItems(oPC,sPowerGem)< nPowerUse)
    {
        //do nothing
        return;
    }

    //Do nothing without a beacon
    object oPort2 = GetLocalObject(oPC,"Port2");
    if (oPort2 != OBJECT_INVALID)
    {
        //remove nPowerUse power stone
        TakeNumItems(oPC,sPowerGem,nPowerUse);

        //Clear existing portals.
        object oPort1 = GetLocalObject(oPC,"Port1");
        if (oPort1 !=OBJECT_INVALID)
        {
            DeleteLocalObject(oPC,"Port1");
            DestroyObject(oPort1);
        }

        //Create a portal object and store its location
        oPort1 = CreateObject(OBJECT_TYPE_PLACEABLE,"portalmarker",lTarget);

        if (oPort1 != OBJECT_INVALID)
        {
            //if this portal is up, set the variables for using it
            SetLocalObject(oPC,"Port1",oPort1);
            SetLocalObject(oPort1,"Dest",oPort2);
            SetLocalObject(oPort1,"Creator",oPC);
            SetLocalObject(oPort2,"Dest",oPort1);
        }
    }
}

Give the file the same name as the 'Tag' of the item (Step 6), ie 'portalstone', and compile it.


Beacon Placeable


1)Start the 'Placeable Wizard' from the 'Wizards' menu at the top of the page.


2)Select 'Visual Effects' for the 'Palette Category' and click on 'Next'.


3)Type in 'Beacon Marker' for the blueprint name, tick the 'Launch Properties Dialog' and then click on 'Finish'.


4)On the 'Basic' properties page, change the name to 'Beacon', but leave the 'Tag' unchanged since several things already have names like Portal etc in the game, we want to be able to distinguish between them. Also tick the 'Plot' and 'Useable' check boxes, which should unset the 'Static' box. Finally change the 'Appearance Type' to 'Magic Sparks', it is suggested that you choose the yellow variety, (which are second in the list) so as to match the portal colour.


5)On the 'Scripts' page, type in 'portal_ou' in the 'OnUsed' script box and then click on the 'Edit' button. 'Ok' the warning dialog that comes up and type in the following:


//::///////////////////////////////////////////////
//::portal_ou Portal OnUsed Event Script
//:://////////////////////////////////////////////
/*
    This script is what actually makes makes the
    beacon and portal placeables work
*/
//:://////////////////////////////////////////////
void main()
{
    object oPC = GetLastUsedBy();

    //check where we are going
    object oDest = GetLocalObject(OBJECT_SELF,"Dest");

    if (oDest != OBJECT_INVALID)
    {
        //Send 'em packing
        object oTeleport = GetFirstFactionMember(oPC,FALSE);
        while (oTeleport != OBJECT_INVALID)
        {
            //Take the PC and anybody who calls them master
            if ((oTeleport == oPC) || (GetMaster(oTeleport) == oPC))
            {
                AssignCommand(oTeleport,ClearAllActions(TRUE));
                AssignCommand(oTeleport,ActionJumpToObject(oDest,FALSE));
            }
            oTeleport = GetNextFactionMember(oPC,FALSE);
        }

        //if this is a beacon
        if (GetTag(OBJECT_SELF) == "BeaconMarker")
        {
            //if this user was the creator of the beacon
            object oCreator = GetLocalObject(OBJECT_SELF,"Creator");
            if (oPC == oCreator)
            {
                //Destroy the portal
                SetPlotFlag(oDest,FALSE);
                DestroyObject(oDest,1.0f);
            }
        }
    }
}

Save and compile the script. Once you are back on the 'Script' properties dialog click on 'Ok' to save the changes.


That's the Beacon placeable done.


Portal Placeable


1)Start the 'Placeable Wizard' from the 'Wizards' menu at the top of the page.


2)Select 'Visual Effects' for the 'Palette Category' and click on 'Next'.


3)Type in 'Portal Marker' for the blueprint name, tick the 'Launch Properties Dialog' and then click on 'Finish'.


4)On the 'Basic' properties page, change the name to 'Portal', but leave the 'Tag' unchanged since several things already have names like Portal etc in the game, we want to be able to distinguish between them. Also tick the 'Plot' and 'Useable' check boxes, which should unset the 'Static' box. Finally change the 'Appearance Type' to 'Portal'.


5)On the 'Scripts' page, type in 'portal_ou' in the 'OnUsed' script box and click on 'Ok' to save the changes.


That's the Portal placeable done.


Finally


The final thing to do is to create a new script that will clean up when players leave the game.


1) Select 'Module Properties' from the edit menu.


2) On the 'Events' tab, type 'cleanpc' in the 'OnClientLeave' event box and click on the 'Edit' button.


3)'Ok' the warning that is displayed and type in the following code:


//::///////////////////////////////////////////////
//::CleanPc Script
//:://////////////////////////////////////////////
/*
    This script is supposed to remove any active portals or beacons
    created by the character, and remove any portal/beacon data from
    local variables stored on the character
*/
//:://////////////////////////////////////////////
void main()
{
    //Get the exiting Creature and see if it is a PC
    object oPC = GetExitingObject();

    if (GetIsPC(oPC)==TRUE)
    {
        //Retrieve any portal info the PC has
        object oPort1 = GetLocalObject(oPC,"Port1");
        object oPort2 = GetLocalObject(oPC,"Port2");

        //Remove any portals left behind
        DestroyObject(oPort1);
        DestroyObject(oPort2);

        //Make sure we have no local variables left on the PC
        DeleteLocalObject(oPC,"Port2");
        DeleteLocalObject(oPC,"Port1");
    }
}

Save and compile the script.


4) Make sure that 'x2_mod_def_act' is in the 'OnActivateItem' event box. Click on Ok to save the changes.


Now you can do a full build on the module, and start testing it.


Things To Play With


Once you have this up and running, you may wish to consider how to distinguish between portals and beacons belonging to different players. It may not be as easy as you think.



A demonstration module is available from here.




 author: Grimlar