Author Topic: RE: Help with Time Functions in Multiplayer  (Read 433 times)

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« on: August 12, 2011, 10:06:58 am »


               I need some more information on the Time functions in multi-player. I'm looking to advance the game clock when a player rests and am not sure if I should use SetTime() or SetCalendar() to do so. According to the Lexicon, SetTime() should never be used in multi-player. I assume this is because it fires only for the player who is resting? Does the same reccomendation apply for SetCalendar()? That is to say, is it just a bad idea in general to advance the game clock in multi-player when a player rests?

I really don't care about which function I use. If its SetTime(), I'll advance the clock eight hours when the rest finishes. If its SetCalendar(), I'll just advance the day by one.
               
               

               


                     Modifié par Pstemarie, 12 août 2011 - 09:12 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #1 on: August 12, 2011, 10:21:48 am »


               Both functions will advance time for whole server/all players. There is no way to advance time in MP without this disadvantage.
               
               

               
            

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #2 on: August 12, 2011, 10:29:06 am »


               Ok, this will work then for what I'm doing since its a single party LAN server. Am I correct in that the rest event does not fire for all players in a party just because one member rests? If this is the case I should probably use ForceRest() on the other party members.
               
               

               


                     Modifié par Pstemarie, 12 août 2011 - 09:29 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #3 on: August 12, 2011, 10:56:40 am »


               Yes correct except ForceRest itself wont fire rest event too. You must fire it manually but if you do this the eventtype will be "invalid" so count with it and workaround it via local variable.
               
               

               
            

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #4 on: August 12, 2011, 11:13:42 am »


               What I've done is fire the OnPlayerRest event once for the PC that initiated the rest. The rest event then cycles through the initiating PC's faction members who are PCs and applies the ForceRest() function to them. At the same time that block of code checks if the resting faction member is a Wizard and if they have their spellbook (if not, spell uses are decremented to pre-rest values).

I went this route because using ActionRest() was firing the rest event for each PC and creating an infinite resting loop that crashed the game - in addition to other undesireable effects (like indexing the clock each time). Heres the final script:


//::///////////////////////////////////////////////
//:: Name: x2_onrest
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
    The generic wandering monster system
*/
//:://////////////////////////////////////////////
//:: Created By: Georg Zoeller
//:: Created On: June 9/03
//:://////////////////////////////////////////////
//:: Modified By: Deva Winblood
//:: Modified Date: January 28th, 2008
//:://////////////////////////////////////////////
#include "x2_inc_restsys"
#include "x2_inc_switches"
#include "x3_inc_horse"
void main()
{
    object oPC = GetLastPCRested();
    object oMount;
    if (!GetLocalInt(GetModule(),"X3_MOUNT_NO_REST_DISMOUNT"))
    { // make sure not mounted
        /*  Deva, Jan 17, 2008
            Do not allow a mounted PC to rest
        */
        if (HorseGetIsMounted(oPC))
        { // cannot mount
            if (GetLocalInt(oPC,"X3_REST_CANCEL_MESSAGE_SENT"))
            { // cancel message already played
                DeleteLocalInt(oPC,"X3_REST_CANCEL_MESSAGE_SENT");
            } // cancel message already played
            else
            { // play cancel message
                FloatingTextStrRefOnCreature(112006,oPC,FALSE);
                SetLocalInt(oPC,"X3_REST_CANCEL_MESSAGE_SENT",TRUE); // sentinel
                // value to prevent message played a 2nd time on canceled rest
            } // play cancel message
            AssignCommand(oPC,ClearAllActions(TRUE));
            return;
        } // cannot mount
    } // make sure not mounted
    if (!GetLocalInt(GetModule(),"X3_MOUNT_NO_REST_DESPAWN"))
    { // if there is a paladin mount despawn it
        oMount=HorseGetPaladinMount(oPC);
        if (!GetIsObjectValid(oMount)) oMount=GetLocalObject(oPC,"oX3PaladinMount");
        if (GetIsObjectValid(oMount))
        { // paladin mount exists
            if (oMount==oPC||!GetIsObjectValid(GetMaster(oMount))) AssignCommand(oPC,HorseUnsummonPaladinMount());
            else { AssignCommand(GetMaster(oMount),HorseUnsummonPaladinMount()); }
        } // paladin mount exists
    } // if there is a paladin mount despawn it
    if (GetModuleSwitchValue(MODULE_SWITCH_USE_XP2_RESTSYSTEM) == TRUE)
    {
        /*  Georg, August 11, 2003
            Added this code to allow the designer to specify a variable on the module
            Instead of using a OnAreaEnter script. Nice new toolset feature!
            Basically, the first time a player rests, the area is scanned for the
            encounter table string and will set it up.
        */
        object oArea = GetArea (oPC);
        string sTable = GetLocalString(oArea,"X2_WM_ENCOUNTERTABLE") ;
        if (sTable != "" )
        {
            int nDoors = GetLocalInt(oArea,"X2_WM_AREA_USEDOORS");
            int nDC = GetLocalInt(oArea,"X2_WM_AREA_LISTENCHECK");
            WMSetAreaTable(oArea,sTable,nDoors,nDC);
            //remove string to indicate we are set up
            DeleteLocalString(oArea,"X2_WM_ENCOUNTERTABLE");
        }

        /* Brent, July 2 2003
           - If you rest and are a low level character at the beginning of the module.
             You will trigger the first dream cutscene
        */
        if (GetLocalInt(GetModule(), "X2_G_LOWLEVELSTART") == 10)
        {
            AssignCommand(oPC, ClearAllActions());
            if (GetHitDice(oPC) >= 12)
            {
                ExecuteScript("bk_sleep", oPC);
                return;
            }
            else
            {
                FloatingTextStrRefOnCreature(84141 , oPC);
                return;
            }
        }
        /*
            Pstemarie, 11 August 2011
            Force the remaining party members to rest
        */
        object oPartyMember = GetFirstFactionMember(oPC);
        while(GetIsObjectValid(oPartyMember) == TRUE)
        {
            AssignCommand(oPartyMember, ClearAllActions());
            SendMessageToPC(oPartyMember, "You rest with the other members of your party.");
            //Check spellbook
            if (GetLevelByclass(class_TYPE_WIZARD, oPartyMember) > 0)
            {
                if (GetItemPossessedBy(oPartyMember, "m1_it_spellbk001") == OBJECT_INVALID)
                {
                    //Store pre-rest values for all memorized spells
                    int nSpell, nUses, nStored;
                    for (nSpell = 0; nSpell < 599; nSpell ++)
                    {
                        nUses = GetHasSpell(nSpell, oPC);
                        if (nUses > 0)
                        {
                            SetLocalInt(oPartyMember, IntToString(nSpell), nUses);
                        }
                    }
                    ForceRest(oPartyMember);
                    //Reset spells to their pre-rest values
                    for (nSpell = 0; nSpell < 599; nSpell ++)
                    {
                        nUses = GetHasSpell(nSpell, oPartyMember);
                        nStored = GetLocalInt(oPartyMember, IntToString(nSpell));
                        nStored = nUses - nStored;
                        for (nUses = 0; nUses < nStored; nUses ++)
                        {
                            DecrementRemainingSpellUses(oPartyMember, nSpell);
                        }
                        DeleteLocalInt(oPartyMember, IntToString(nSpell));
                    }
                    SendMessageToPC(oPartyMember, "You cannot regain spells without a spellbook.");
                }
                else
                {
                    ForceRest(oPartyMember);
                }
            }
            //Check spellbook
            else
            {
                ForceRest(oPartyMember);
            }
            oPartyMember = GetNextFactionMember(oPC);
        }
        if (GetLastRestEventType()==REST_EVENTTYPE_REST_STARTED)
        {
            if (!WMStartPlayerRest(oPC))
            {
                // The resting system has objections against resting here and now
                // Probably because there is an ambush already in progress
                FloatingTextStrRefOnCreature(84142  ,oPC);
                AssignCommand(oPC,ClearAllActions());
            }
            if (WMCheckForWanderingMonster(oPC))
            {
                //This script MUST be run or the player won't be able to rest again ...
                ExecuteScript("x2_restsys_ambus",oPC);
            }
            //Check spellbook
            /*
                Pstemarie, 11 August 2011
                Do not allow a wizard to regain spells without a spellbook
                Step 1 - Store remaining uses for memorized spells
            */
            if (GetLevelByclass(class_TYPE_WIZARD, oPC) > 0)
            {
                if (GetItemPossessedBy(oPC, "m1_it_spellbk001") == OBJECT_INVALID)
                {
                    //Store pre-rest values for all memorized spells
                    int nSpell, nUses;
                    for (nSpell = 0; nSpell < 599; nSpell ++)
                    {
                        nUses = GetHasSpell(nSpell, oPC);
                        if (nUses > 0)
                        {
                            SetLocalInt(oPC, IntToString(nSpell), nUses);
                        }
                    }
                }
            }
            //Check spellbook
        }
        else if (GetLastRestEventType()==REST_EVENTTYPE_REST_CANCELLED)
        {
            // No longer used but left in for the community
            // WMFinishPlayerRest(oPC,TRUE); // removes sleep effect, etc
        }
        else if (GetLastRestEventType()==REST_EVENTTYPE_REST_FINISHED)
        {
            // No longer used but left in for the community
            // WMFinishPlayerRest(oPC); // removes sleep effect, etc
            //Check spellbook
            /*
                Pstemarie, 11 August 2011
                Do not allow a wizard to regain spells without a spellbook
                Step 2 - Reset remaining uses for memorized spells
            */
            if (GetLevelByclass(class_TYPE_WIZARD, oPC) > 0)
            {
                if (GetItemPossessedBy(oPC, "m1_it_spellbk001") == OBJECT_INVALID)
                {
                    //Reset spells to their pre-rest values
                    int nSpell, nUses, nStored;
                    for (nSpell = 0; nSpell < 599; nSpell ++)
                    {
                        nUses = GetHasSpell(nSpell, oPC);
                        nStored = GetLocalInt(oPC, IntToString(nSpell));
                        nStored = nUses - nStored;
                        for (nUses = 0; nUses < nStored; nUses ++)
                        {
                            DecrementRemainingSpellUses(oPC, nSpell);
                        }
                        DeleteLocalInt(oPC, IntToString(nSpell));
                    }
                    SendMessageToPC(oPC, "You cannot regain spells without a spellbook.");
                }
            }
            //Check spellbook
            //Advance game clock
            /*
                Pstemarie, 11 August 2011
                Advance the game clock 8 hours when a player rests
            */
            int nHour = GetTimeHour();
            int nMin  = GetTimeMinute();
            int nSec  = GetTimeSecond();
            int nMSec = GetTimeMillisecond();
            nHour += 8;
            SetTime(nHour, nMin, nSec, nMSec);
            //Advance game clock
        }
    }
}

               
               

               


                     Modifié par Pstemarie, 12 août 2011 - 10:14 .
                     
                  


            

Legacy_Axe_Murderer

  • Full Member
  • ***
  • Posts: 199
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #5 on: August 12, 2011, 11:22:27 am »


               There is only one game clock/calendar shared by everyone (including NPCs and placeables etc). Changing it will affect everybody and everything. Advancing the clock affects all temporary effects in the module, and I presume that's the main reason why it isn't recommended in MP games. Anything with a temporary buff or penalty or other effect which is supposed to last less than the time you advance the clock will lose it. Any delayed commands will expire immediately or at the very least sooner. Stuff like that.
It will be a real bummer if you and your party are standing outside the dragon's lair taking 2 minutes to buff all up and right when you go inside some joker on the other side of the world decides to rest which advances the clock making all your buffs poof vanish before the first attack is delivered...especially if you find a whole hoard of dragons waiting for you inside!

Whether you use calendar functions or time functions is dependent on how much of a change you want to make. The calendar functions affect days, months and years...the time functions- hours minutes & seconds. Minutes are wierd because that's where real-time vs game-time is resolved based on the module property MinutesPerHour. Typically there are not 60 game minutes in a game hour. Usually minutes goes from 0 to 2 or 3 then the hour advances and minutes goes back to 0. Calendar advances when hours roll over from 23 to 0.

Also you cannot set the clock backwards.
               
               

               


                     Modifié par Axe_Murderer, 12 août 2011 - 10:23 .
                     
                  


            

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #6 on: August 12, 2011, 11:36:30 am »


               Thanks Axe for the info. The Server is a "LAN/Private" Server so I'll never have to worry about the "some joker..." scenario you put forth. However it is good information to know if I ever take the PW live. For now though its just a single party of six players and myself.

All this really is more of an experiment than anything else to see if we can make NWN somewhat more resemble pnp without getting into all the fancy client extenders like NWNX. We like pnp, but we really aren't so much into the sitting around a table together and "role-playing". Instead we'd rather explore 3D environments that we can interact with on a level you just can't get in pnp.

I must say so far I'm quite amazed at what I've been able to accomplish without using client extenders:

1. Foci and Material Components for Spells.
2. Spellbooks required for Wizards to regain spells.
3. PnP Injury and Death - using the BioWare function for it - EffectHitPointChangeWhenDying().
4. Expendable Torches and Lanterns.
5. PnP Object Breaking for placeables and doors.
6. PnP Heal skill.

It really is quite remarkable just how much power has been preprogrammed into the engine.
               
               

               


                     Modifié par Pstemarie, 12 août 2011 - 10:47 .
                     
                  


            

Legacy_Axe_Murderer

  • Full Member
  • ***
  • Posts: 199
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #7 on: August 12, 2011, 11:52:49 am »


               Yeah, it's orders of magnitude less punative and manageable in a single party MP game.

I agree, games that support a scripting language are a very nice thing 'Posted
               
               

               


                     Modifié par Axe_Murderer, 12 août 2011 - 10:53 .
                     
                  


            

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #8 on: August 12, 2011, 01:41:49 pm »


               There! I finally got it working - was getting a "too many operations" error - so that if any party member rests then all members of the party use ForceRest to replenish HP, feats, spells, etc. I even managed to loop in the spellbook requirement for Wizards to replenish spells using two subscripts - one to store the spell uses and one to reset the spell uses to their pre-rest values if no spellbook is found in the wizard's inventory.

As far as the "more manageable" that only stands to reason since, if you look back at a lot of the EARLY interviews and commentary by the designers concerning multi-player, they never envisioned a multi-party environment in the initial development stages. Multi-player was originally envisioned to be more like your typical pnp game of D&D - one party of like-minded players/characters facing off against enemies (ala Baldur's Gate - from which NWN is clearly derived).
               
               

               


                     Modifié par Pstemarie, 12 août 2011 - 12:48 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #9 on: August 12, 2011, 02:33:18 pm »


               btw could you send me your dying script ? Im looking for some easy to implement/undestand efficient scipt that reflect pnp as much as possible.
               
               

               
            

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #10 on: August 12, 2011, 03:41:34 pm »


               Here you go ShaDoOoW. I just put the visual in there for a sort of "ticking" timer.

/*
Filename: nw_o0_dying.nss
Author: Paul Ste. Marie (Pstemarie)
Date Created: 12 August 2011
Summary: OnPlayerDying Event
Dying player takes 1 damage per round until dead.

--------------------------------------------------------------------------------
Post-Release Revision History

Revision Author:
Revision Date:
Revision Summary:

*/

void main()
{
     effect eDamage = EffectHitPointChangeWhenDying(-1.0);
     effect eVis = EffectVisualEffect(VFX_COM_BLOOD_SPARK_SMALL);
     ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, GetLastPlayerDying());
     ApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, GetLastPlayerDying());
}
               
               

               


                     Modifié par Pstemarie, 12 août 2011 - 02:43 .
                     
                  


            

Legacy_Axe_Murderer

  • Full Member
  • ***
  • Posts: 199
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #11 on: August 12, 2011, 05:41:54 pm »


               

Pstemarie wrote...

Here you go ShaDoOoW. I just put the visual in there for a sort of "ticking" timer.
...

Except it won't tick. The OnPlayerDying event only happens once. When you go below 1HP the engine fires either an OnDeath or an OnDying event depending on whether you made it all the way down to -10 or not . Only one event is generated. The effect your using is like a wounding effect. It automatically applies itself once per round and automatically removes itself when the player gets back above 0 or down to -10 (then the engine fires OnDeath).
If you were to use a regular damage effect there instead, the player would only get one application of it and then be forever incapacitated unless it was enough to kill him, or somebody comes along and heals him, or the module somehow decides he should be healed or killed and does so. That's why the WhenDying effect exists and why you apply it using a permanent duration and why you can't use it as a wounding effect on living creatures.

So your visual effect will only occur once at the start of bleeding.

To make a pulse effect to go along with it:

[nwscript]void PulseBlood()
{ if( GetIsDead( OBJECT_SELF ) || (GetCurrentHitPoints() > 0) ) return;
   ApplyEffectToObject( DURATION_TYPE_INSTANT, EffectVisualEffect( VFX_COM_BLOOD_SPARK_SMALL  ), OBJECT_SELF );
   DelayCommand( RoundsToSeconds( 1 ), PulseBlood() );
}

void main()
{ object oPC = GetLastPlayerDying();
   ApplyEffectToObject( DURATION_TYPE_PERMANENT, EffectHitPointChangeWhenDying( 1.0 ), oPC );
   AssignCommand( oPC, PulseBlood() );
}[/nwscript]
               
               

               


                     Modifié par Axe_Murderer, 12 août 2011 - 04:47 .
                     
                  


            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #12 on: August 12, 2011, 06:22:01 pm »


               Axe, is it more efficient to recursively call a function, or execute a script that calls itself? Are they the same?

I handled dying similarly in my mod, but instead of calling a function in my dying script, I executed a bleed script that iterates once a round. My thought was that the dying script and all its variables would remain in use until all the delays called within it were complete. Therefore for cleaner garbage collection, I thought executing a separate script would be more efficient - because the entire script would purge itself each time it executed.
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #13 on: August 12, 2011, 07:09:16 pm »


               

henesua wrote...

Axe, is it more efficient to recursively call a function, or execute a script that calls itself? Are they the same?

I handled dying similarly in my mod, but instead of calling a function in my dying script, I executed a bleed script that iterates once a round. My thought was that the dying script and all its variables would remain in use until all the delays called within it were complete. Therefore for cleaner garbage collection, I thought executing a separate script would be more efficient - because the entire script would purge itself each time it executed.

Re-executing is more efficient as recursive funtions keep stacking variables in memory or something like that, the more parameters in function the worse and definitely avoid structs in recursive functions. Anyway for small functions like this it shouldnt matter it has differences only when its huge script with many parameters repeating many times.

Pstemarie: thanks, but unfortunately that doesnt suit me as I would like to give chance to regenerate if player overcome some DC check like in pnp IIRC.
               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
RE: Help with Time Functions in Multiplayer
« Reply #14 on: August 12, 2011, 07:11:06 pm »


               Shadow, my customizable death script is close to what you are asking for. Its not completely stripped down, but fairly spare.

And thanks for confirming my suspicions about recursive functions versus recursive script execution.