Moveable Placeables

In response to this thread I created a library of functions to make placeables appear to move. I'm posting it here as I don't want to release it to the NWVault in its current form: there is a lot of functionality I would like to add but it requires a rethink on how we go about defining the move.

If you are interested in seeing the code in action download the Module from the links below, unzip it to your My Documents\Neverwinter Nights\Modules directory.

If you are interested in using the code either copy the source code into a new script in your module and save it as sj_move_plc_inc or download the ERF from the link below, unzip it and import it into your module.

Downloads

Source Code

//--------------------------------------------------------------------------------------------------
//  sj_move_plc_inc
//--------------------------------------------------------------------------------------------------
/*
    A library of functions to "move" placeable objects.
    
    Placeables, by definition, cannot move. There were to work-arounds for this in NWN1: either a 
    custom creature was created from the placeable's appearance and it was moved; or a series of 
    clones were quickly created and destroyed with an appropriate offset to give the illusion of 
    movement. The first technique is still valid for NWN2 however the latter is longer viable as 
    objects take far too long to appear and disappear in NWN2.
    
    To overcome NWN2's limitations new MovePlaceable* functions will create an array of invisible 
    objects between the original object and the destination object or location. The objects are 
    temporarily revealed in sequence to give the illusion of movement. Finally the original object
    and all but the last of the array objects are destroyed.
    
   
    USAGE NOTES
    ===========
    
    - the placeable object to be moved must have its own blueprint, it must be non-static and have
      dynamic collisions; and it must have a unique tag
    
    - any object reference to the original placeable will become invalid after the move so, for 
      example, to move the placeable back a new reference must be obtained using GetObjectByTag etc.
    
    - ensure that each placeable only receives one move command at a time, for example by making the
      switch unusable, as concurrent commands will produce undesired effects and rogue placeables
    
    - the impact on performance and the smoothness of the move can be controlled using the nSteps 
      and fDuration arguments on the MovePlaceable* functions: 12 frames/s and 4 frames/m provides
      a reasonably smooth effect.

    
    TODO @ 05 Apr 2008
    ==================
    
    - add the ability to rotate the object (both internally and externally)
    - convert to an action to prevent concurrent command?
    - rewrite to use a custom struct?
    - rewrite to use pseudo-arrays?
*/
//--------------------------------------------------------------------------------------------------
/*
    Created: 05 Apr 2008 - Sunjammer
*/
//--------------------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------------------
//  PROTOTYPES
//--------------------------------------------------------------------------------------------------

// Helper function to create an array of objects between the original object and the destination.
//  - oOriginal:        the original placeable
//  - nCount:           the number of tweening objects
//  - vDestination:     destination position
void CreateTweeningObjects(object oOriginal, int nCount, vector vDestination);

// Helper function to destroy the original object and all but the last of the array of objects between
// the original object and the destination.
//  - oOriginal:        the original placeable
//  - nCount:           the number of tweening objects
//  * Returns:          noteR
//  * OnError:          noteE
void DestroyTweeningObjects(object oOriginal, int nCount);

// Makes a placeable appear to move by creating a series of invisible clones between its location
// and the destination location and then temporarily revealing in sequence.
//  - oPlaceable:       the original placeable
//  - lDestination:     location of the destination
//  - nSteps:           the number of steps in the move (affects performance and smoothness)
//  - fDelay:           the delay (normally 0.5 seconds) to allow for object creation
//  - fDuration:        the duration of the move (affects performance and smoothness) 
//  - oSound:           a sound object to be played during the move
void MovePlaceableToLocation(object oPlaceable, location lDestination, int nSteps, float fDelay, float fDuration, object oSound=OBJECT_INVALID);

// Makes a placeable appear to move by creating a series of invisible clones between its location
// and the destination object and then temporarily revealing in sequence.
//  - oPlaceable:       the original placeable
//  - oDestination:     an object (normally a waypoint) indicating the destination
//  - nSteps:           the number of steps in the move (affects performance and smoothness)
//  - fDelay:           the delay (normally 0.5 seconds) to allow for object creation
//  - fDuration:        the duration of the move (affects performance and smoothness) 
//  - oSound:           a sound object to be played during the move
void MovePlaceableToObject(object oPlaceable, object oDestination, int nSteps, float fDelay, float fDuration, object oSound=OBJECT_INVALID);

// Removes all Cutscene Invisibility visual effects from an object.
//  - oObject:          object with CSI visual effect
void RemoveCutsceneInvisibility(object oObject);

// Helper function to reveal each tweening objects in sequence to give the illusion of movement.
// original object is in fact moving.
//  - oOriginal:        the original placeable
//  - nCount:           the number of tweening objects
//  - fDuration:        the duration of the tween, i.e. the "move"
void RevealTweeningObjects(object oOriginal, int nCount, float fDuration);


//--------------------------------------------------------------------------------------------------
//  FUNCTIONS
//--------------------------------------------------------------------------------------------------

void CreateTweeningObjects(object oOriginal, int nCount, vector vDestination)
{
    int n;
    object oNew;
    
    effect eCSI = EffectVisualEffect(VFX_DUR_CUTSCENE_INVISIBILITY);
    
    // decompose the original object
    string sRes = GetResRef(oOriginal);
    string sTag = GetTag(oOriginal);

    // decompose the original location
    object oArea = GetArea(oOriginal);
    vector vPosition = GetPosition(oOriginal);
    float fFacing = GetFacing(oOriginal);

    // calculate the translation vector for each tween
    vector vTranslation = (vDestination - vPosition) / IntToFloat(nCount);

    // create tweening objects between original object and the destination
    // NOTE: that for GetObjectByTag purposes the original object starts as nNth = 0 but is pushed
    // down the stack until nNth = nCount and the last tweening object (i.e. the one we want to 
    // persist) becomes nNth = 0
    for(n = 0; n < nCount; n++)
    {
        vPosition += vTranslation;
    
        // create a new object and instantly make it cutscene-invisible
        oNew = CreateObject(OBJECT_TYPE_PLACEABLE, sRes, Location(oArea,  vPosition, fFacing), FALSE, sTag);
        ApplyEffectToObject(DURATION_TYPE_PERMANENT, eCSI, oNew);
    }
}


void DestroyTweeningObjects(object oOriginal, int nCount)
{
    int n;
    
    // decompose object
    string sTag = GetTag(oOriginal);

    // destroy the original object and all tweening objects except the last 
    // NOTE: that for GetObjectByTag purposes the original object starts as nNth = 0 but is pushed
    // down the stack until nNth = nCount and the last tweening object (i.e. the one we want to 
    // persist) becomes nNth = 0
    for(n = nCount; n > 1; n--)
    {
        DestroyObject(GetObjectByTag(sTag, n));
    }
}


void RemoveCutsceneInvisibility(object oObject)
{
    effect eEffect = GetFirstEffect(oObject);
    while(GetIsEffectValid(eEffect))
    {
        // only remove the effect if it is a cutscene invisible visual effect
        // NOTE: the first entry in a visual effect's (0-based) IntList is the visual effect id
        if(GetEffectType(eEffect) == EFFECT_TYPE_VISUALEFFECT
        && GetEffectInteger(eEffect, 0) == VFX_DUR_CUTSCENE_INVISIBILITY)
        {
            RemoveEffect(oObject, eEffect);
        }
        eEffect = GetNextEffect(oObject);
    }

}


void RevealTweeningObjects(object oTarget, int nCount, float fDuration)
{
    int n;
    
    effect eCSI = EffectVisualEffect(VFX_DUR_CUTSCENE_INVISIBILITY);
    float fInterval = fDuration / nCount;
    string sTag = GetTag(oTarget);

    // NOTE: that for GetObjectByTag purposes the original object starts as nNth = 0 but is pushed
    // down the stack until nNth = nCount and the last tweening object (i.e. the one we want to 
    // persist) becomes nNth = 0
    for(n = 0; n < nCount; n++)
    {
        // hide the previous object and reveal the next
        DelayCommand(fInterval * (nCount - n), ApplyEffectToObject(DURATION_TYPE_PERMANENT, eCSI, GetObjectByTag(sTag, n + 1)));
        DelayCommand(fInterval * (nCount - n), RemoveCutsceneInvisibility(GetObjectByTag(sTag, n)));
    }
}


void MovePlaceableToLocation(object oPlaceable, location lDestination, int nSteps, float fDelay, float fDuration, object oSound=OBJECT_INVALID)
{
    // create, reveal and destroy all the tweening objects
    CreateTweeningObjects(oPlaceable, nSteps, GetPositionFromLocation(lDestination));
    DelayCommand(fDelay, RevealTweeningObjects(oPlaceable, nSteps, fDuration));
    DelayCommand(fDelay + fDuration, DestroyTweeningObjects(oPlaceable, nSteps));

    // start and stop the sound object (if appropriate)
    if(GetIsObjectValid(oSound))
    {
        DelayCommand(fDelay, SoundObjectPlay(oSound));
        DelayCommand(fDelay + fDuration, SoundObjectStop(oSound));
    }
}


void MovePlaceableToObject(object oPlaceable, object oDestination, int nSteps, float fDelay, float fDuration, object oSound=OBJECT_INVALID)
{
    MovePlaceableToLocation(oPlaceable, GetLocation(oDestination), nSteps, fDelay, fDuration, oSound);
}

Email: comments or bugs