You are here

Add rest restrictions to Storm of Zehir?

39 posts / 0 new
Last post
milspec
Add rest restrictions to Storm of Zehir?

Hi all. In anticipation of an upcoming 4-person play-through of Storm of Zehir, I have added a number of modifications to make NWN2 more like 3.5E PnP. (Spell Fixes and Improvements, Monster AI, etc.)

 

I would like to add a rest restriction to stop PCs from constantly resting (on the world map) but cannot figure out how. I tried to add HCR2 briefly, since that is the "purest" form of PnP resting, but that seems impossible given the large number of custom action scripts. Next best would be a simple rest timer like this:

 

http://nwn.wikia.com/wiki/Restrict_how_often_PCs_can_rest

 

Does anyone know a way to get that working with the SoZ rest system? Any way to restrict resting on the world map, for example to once per in-game day? Any other suggested hacks, like requiring an object to rest and making it expensive?

 

Thanks,

 

milspec

 

PS: I added the Combat Auto-Pause mod to the other mods (like Spell Improvements) by manually editing the spells.2da and feats.2da, which is about the extent of my hacking ability.

  • up
    50%
  • down
    50%
kevL's

This is very (dare i say extremely) crude. Trying to modify resting, in anything like a simple manner, for the SoZ overland map quickly proved rather screwy. 2 things being (a) the rest event fires twice, and (b) the value of GetLastRestEventType() isn't used straighforwardly.

[tech note: 'ginc_restsys' is used to invoke the gui 'gui_rest']

So, what follows is the stock SoZ script 'k_mod_player_rest'. It usually resides in the SoZ Campaign folder (.nss/.ncs files). Its behavior is simply to check that the player hasn't clicked Rest on the same numerical day. It doesn't matter if the rest was successful, or cancelled, or whatever player can't even try to rest twice on the same day. Like I said, it's crude ...

note that it might require an initial rest or two on the OL to set variables before it starts to work. (Resting in SoZ taverns typically bypasses this rest event, using a dialog script to force-rest instead. So tavern resting shouldn't affect this or vice versa)

Oh, one last thing: I can't test this for MP. I am *hoping* that variables on a player-object will persist between loads. I believe this behavior was implemented in Nwn2 (am pretty sure that it isn't for Nwn1, which would simply mean that a player can logout/login to reset the vars /shrug, i'm not gonna bang my head against this *that much* hope you understand - but if you need a bit of assistance i'm okay with that ofc ).


ps. Beware of any other mods that may have changed your 'k_mod_player_rest' ... ie, search for it in /Override etc.


// k_mod_player_rest
/*
    Module rest
*/
// ChazM 3/2/06
// ChazM 11/29/06
// ChazM 4/10/07 - added GUI functions (disabled for the time being until fully working)
// ChazM 4/11/07 - Finished and enabled Rest GUI changes. Moved parts to gui_rest
// ChazM 4/13/07 - Added support for rest strings output through script
// ChazM 7/18/07 - Added delay before popping up GUI to prevent multiple rest clicks while paused.
// TDE 6/20/08 - Adapted script for NX2
// NLC 10/10/08 - Finalized NX2 Rest System.

#include "ginc_restsys"
#include "x2_inc_switches"
#include "ginc_time"
#include "ginc_transition"


// kL_rest start ->
const string LASTRESTYEAR  = "lastrestyear";
const string LASTRESTMONTH = "lastrestmonth";
const string LASTRESTDAY   = "lastrestday";

// Sets the year, month, and day of the last PC-rest
// as variables on that PC.
void SetRestRestriction(object oPC)
{
    SetLocalInt(oPC, LASTRESTYEAR,  GetCalendarYear());
    SetLocalInt(oPC, LASTRESTMONTH, GetCalendarMonth());
    SetLocalInt(oPC, LASTRESTDAY,   GetCalendarDay());
}

// Returns TRUE if PC can rest.
// - true : not OverlandMap (other restrictions may apply to other areas)
//          is not the same literal day that PC last rested.
int CheckRestRestriction(object oPC)
{
    if (GetIsOverlandMap(GetArea(oPC))
        && GetCalendarYear()  == GetLocalInt(oPC, LASTRESTYEAR)
        && GetCalendarMonth() == GetLocalInt(oPC, LASTRESTMONTH)
        && GetCalendarDay()   == GetLocalInt(oPC, LASTRESTDAY))
    {
        return FALSE;
    }
    return TRUE;
}
// kl_rest end.


// prototypes
void DoSinglePartyRest(object oPC);

// funcitons
void DoSinglePartyRest(object oPC)
{
    //WMRestEncounterInit(oPC);
    PrettyDebug("Using Single Party Rest System (switch set)!");
    // if you press the rest button, an interface pops up alerting you to the danger level,
    // and asking how long you want to rest.
    if (GetLocalInt(oPC, VAR_REST_NOW) == FALSE)
    {
        AssignCommand(oPC, ClearAllActions());
        if (!IsPartyGathered(oPC, 20.0f))
        {
            //AssignCommand(oPC, ActionSpeakString("Hey, Let's all gather together so we can rest!"));
            FloatingTextStrRefOnCreature(STR_REF_MUST_GATHER_FOR_REST, oPC);
        }
        else
        {
            //AssignCommand(oPC, ActionStartConversation(oPC, "gr_rest_convo", TRUE, FALSE));
            // conversation must set DoRestingNow to TRUE and make player rest
            DelayCommand(0.01f, DisplayRestGUI(oPC));
        }
    }
    else
    {
        // Rest away!
    }
}

void main()
{
    object oPC = GetLastPCRested();
    AssignCommand(oPC, ClearAllActions());

    // kL_rest start ->
    if (!CheckRestRestriction(GetOwnedCharacter(oPC)))
    {
        return;
    }
    SetRestRestriction(GetOwnedCharacter(oPC));
    // kl_rest end.

    PrettyDebug(GetName(oPC) + " is Resting!");
    int iRestEventType = GetLastRestEventType();
    int bSinglePartyRestSystem = GetModuleSwitchValue(MODULE_SWITCH_USE_NX1_SINGLE_PARTY_RESTSYSTEM);

    switch (iRestEventType)
    {
        case REST_EVENTTYPE_REST_STARTED:
        {
            //RestSendMessage(oPC, STR_REF_REST_STARTED);
            PrettyDebug("this is REST_EVENTTYPE_REST_STARTED - so checking for wandering monsters...");
            if (bSinglePartyRestSystem == TRUE)
            {
                DoSinglePartyRest(oPC);
            }
            else if (GetModuleSwitchValue(MODULE_SWITCH_USE_XP2_RESTSYSTEM) == TRUE)
            {
                RestSendMessage(oPC, STR_REF_REST_STARTED);
                WMRestEncounterInit(oPC);
                WMRestEncounterCheck(oPC);
            }
            else
            {
                RestSendMessage(oPC, STR_REF_REST_STARTED);
            }
        }
        break;

        case REST_EVENTTYPE_REST_CANCELLED:
        {
            int bReportCancel = GetLocalInt(oPC, VAR_REST_REPORT_CANCEL);
            if ((!bSinglePartyRestSystem) || bReportCancel)
                RestSendMessage(oPC, STR_REF_REST_CANCELLED);
         // No longer used but left in for the community
         // WMFinishPlayerRest(oPC,TRUE); // removes sleep effect, etc
        }
        break;

        case REST_EVENTTYPE_REST_FINISHED:
        {
            // always indicate rest finished
            RestSendMessage(oPC, STR_REF_REST_FINISHED);
         // No longer used but left in for the community
         //   WMFinishPlayerRest(oPC); // removes sleep effect, etc
        }
    }
}

  • up
    100%
  • down
    0%
milspec

Thanks very much. I understand the explanation, and will test it soon and report back. I appreciate the effort! 

  • up
    50%
  • down
    50%
milspec

I tested in a new Campaign of SoZ. It works in SP. I was able to rest without limits in the tavern. On the world map I was able to rest on Day 2, waking at 6am on Day 3, then not rest again (not even getting the GUI) all through Day 3. As soon as it changed to Day 4 I was able to rest again.

I will test in MP with my laptop connected as a second player and report back.

  • up
    50%
  • down
    50%
kevL's

I'm unsure how MP handles resting in SoZ, ie. does the whole party rest when any player clicks Rest?

if so, i'll change my functions to loop over all PCs ... (else one guy can click rest, then another guy can click rest, etc.)

  • up
    50%
  • down
    50%
milspec

Hi KevL,

I tested in MP. It allows each character to rest once on the world map, and each character cannot repeat the rest action for the same day. This means that a party of 4 separate players in MP can rest 4 times on the world map.

Any easy way to restrict it to party? I think the ideal behavior would be to only allow the party of 4 to rest once per day on the world map.

The good news is I have tested it many times now, and it seems to be very stable and reliable. :-)

Thanks,

milspec

 

  • up
    50%
  • down
    50%
kevL's

try this one out

// k_mod_player_rest
/*
    Module rest
*/
// ChazM 3/2/06
// ChazM 11/29/06
// ChazM 4/10/07 - added GUI functions (disabled for the time being until fully working)
// ChazM 4/11/07 - Finished and enabled Rest GUI changes. Moved parts to gui_rest
// ChazM 4/13/07 - Added support for rest strings output through script
// ChazM 7/18/07 - Added delay before popping up GUI to prevent multiple rest clicks while paused.
// TDE 6/20/08 - Adapted script for NX2
// NLC 10/10/08 - Finalized NX2 Rest System.

#include "ginc_restsys"
#include "x2_inc_switches"
#include "ginc_time"
#include "ginc_transition"


// kL_rest start ->
const string LASTRESTYEAR  = "lastrestyear";
const string LASTRESTMONTH = "lastrestmonth";
const string LASTRESTDAY   = "lastrestday";

// Sets the year, month, and day of the last PC-rest as variables on all PCs
// that are currently logged in.
void SetRestRestriction()
{
    int iYear  = GetCalendarYear();
    int iMonth = GetCalendarMonth();
    int iDay   = GetCalendarDay();

    object oOwnedPC = GetFirstPC();
    while (GetIsObjectValid(oOwnedPC))
    {
        SetLocalInt(oOwnedPC, LASTRESTYEAR,  iYear);
        SetLocalInt(oOwnedPC, LASTRESTMONTH, iMonth);
        SetLocalInt(oOwnedPC, LASTRESTDAY,   iDay);

        oOwnedPC = GetNextPC();
    }
}

// Checks if this rest should be allowed.
// - true : is not OverlandMap (other restrictions may apply to other areas)
//          is not the same literal day that any PC last rested.
int CheckRestRestriction(object oOwnedPC)
{
    if (GetIsOverlandMap(GetArea(oOwnedPC)))
    {
        int iYear  = GetCalendarYear();
        int iMonth = GetCalendarMonth();
        int iDay   = GetCalendarDay();

        oOwnedPC = GetFirstPC();
        while (GetIsObjectValid(oOwnedPC))
        {
            if (   GetLocalInt(oOwnedPC, LASTRESTDAY)   == iDay
                && GetLocalInt(oOwnedPC, LASTRESTMONTH) == iMonth
                && GetLocalInt(oOwnedPC, LASTRESTYEAR)  == iYear)
            {
                return FALSE;
            }
            oOwnedPC = GetNextPC();
        }
    }
    return TRUE;
}

/* Sets the year, month, and day of the last PC-rest
// as variables on that PC.
void SetRestRestriction(object oPC)
{
    SetLocalInt(oPC, LASTRESTYEAR,  GetCalendarYear());
    SetLocalInt(oPC, LASTRESTMONTH, GetCalendarMonth());
    SetLocalInt(oPC, LASTRESTDAY,   GetCalendarDay());
}

// Returns TRUE if PC can rest.
// - true : not OverlandMap (other restrictions may apply to other areas)
//          is not the same literal day that PC last rested.
int CheckRestRestriction(object oPC)
{
    if (GetIsOverlandMap(GetArea(oPC))
        && GetCalendarYear()  == GetLocalInt(oPC, LASTRESTYEAR)
        && GetCalendarMonth() == GetLocalInt(oPC, LASTRESTMONTH)
        && GetCalendarDay()   == GetLocalInt(oPC, LASTRESTDAY))
    {
        return FALSE;
    }
    return TRUE;
} */
// kL_rest end.


// prototypes
void DoSinglePartyRest(object oPC);

// funcitons
void DoSinglePartyRest(object oPC)
{
    //WMRestEncounterInit(oPC);
    PrettyDebug("Using Single Party Rest System (switch set)!");
    // if you press the rest button, an interface pops up alerting you to the danger level,
    // and asking how long you want to rest.
    if (GetLocalInt(oPC, VAR_REST_NOW) == FALSE)
    {
        AssignCommand(oPC, ClearAllActions());
        if (!IsPartyGathered(oPC, 20.0f))
        {
            //AssignCommand(oPC, ActionSpeakString("Hey, Let's all gather together so we can rest!"));
            FloatingTextStrRefOnCreature(STR_REF_MUST_GATHER_FOR_REST, oPC);
        }
        else
        {
            //AssignCommand(oPC, ActionStartConversation(oPC, "gr_rest_convo", TRUE, FALSE));
            // conversation must set DoRestingNow to TRUE and make player rest
            DelayCommand(0.01f, DisplayRestGUI(oPC));
        }
    }
    else
    {
        // Rest away!
    }
}

void main()
{
    object oPC = GetLastPCRested();
    AssignCommand(oPC, ClearAllActions());

    // kL_rest start ->
    if (!CheckRestRestriction(GetOwnedCharacter(oPC)))
    {
        return;
    }
    SetRestRestriction();
    // kL_rest end.

    PrettyDebug(GetName(oPC) + " is Resting!");
    int iRestEventType = GetLastRestEventType();
    int bSinglePartyRestSystem = GetModuleSwitchValue(MODULE_SWITCH_USE_NX1_SINGLE_PARTY_RESTSYSTEM);

    switch (iRestEventType)
    {
        case REST_EVENTTYPE_REST_STARTED:
        {
            //RestSendMessage(oPC, STR_REF_REST_STARTED);
            PrettyDebug("this is REST_EVENTTYPE_REST_STARTED - so checking for wandering monsters...");
            if (bSinglePartyRestSystem == TRUE)
            {
                DoSinglePartyRest(oPC);
            }
            else if (GetModuleSwitchValue(MODULE_SWITCH_USE_XP2_RESTSYSTEM) == TRUE)
            {
                RestSendMessage(oPC, STR_REF_REST_STARTED);
                WMRestEncounterInit(oPC);
                WMRestEncounterCheck(oPC);
            }
            else
            {
                RestSendMessage(oPC, STR_REF_REST_STARTED);
            }
        }
        break;

        case REST_EVENTTYPE_REST_CANCELLED:
        {
            int bReportCancel = GetLocalInt(oPC, VAR_REST_REPORT_CANCEL);
            if ((!bSinglePartyRestSystem) || bReportCancel)
                RestSendMessage(oPC, STR_REF_REST_CANCELLED);
         // No longer used but left in for the community
         // WMFinishPlayerRest(oPC,TRUE); // removes sleep effect, etc
        }
        break;

        case REST_EVENTTYPE_REST_FINISHED:
        {
            // always indicate rest finished
            RestSendMessage(oPC, STR_REF_REST_FINISHED);
         // No longer used but left in for the community
         //   WMFinishPlayerRest(oPC); // removes sleep effect, etc
        }
    }
}

  • up
    100%
  • down
    0%
kevL's

oops, I see an optimization ... in CheckRestRestriction()

day should be checked first, then month, then year.


hold on a sec I'll change it.

edit: done

  • up
    50%
  • down
    50%
milspec

Yes, it works. Only one player can start a rest action per day. Any other rest action from any other player is canceled. The day has to advance to the next day before any single player can try another one rest action. That is great.

You already mentioned the limitations above, for example if the rest if canceled or interrupted then the players can still not rest until the next day. That is OK. The worst that happens is we need to head back to a city to rest in an Inn.

Thanks very much! We play in a few weeks, I will report back then.

milspec

  • up
    50%
  • down
    50%
kevL's
  • You already mentioned the limitations above, for example if the rest if canceled or interrupted then the players can still not rest until the next day. That is OK. The worst that happens is we need to head back to a city to rest in an Inn.

there is a stock script that can be run from the console to advance time. But i won't say what it is unless you explictly ask for it ...

  • Thanks very much! We play in a few weeks, I will report back then.

great. 4-player coop LAN-style you have my kudos


and i'm usually willing to code a doohickey or fix a thing or two ....

  • up
    50%
  • down
    50%
kevL's

these functions and calls are (should be) much more efficient than previous:

// kL_rest start ->
// Gets the current GT in minutes.
int GetGT()
{
    return  GetTimeMinute()
         +  GetTimeHour()           * 60
         + (GetCalendarDay() - 1)   * 60 * 24
         + (GetCalendarMonth() - 1) * 60 * 24 * 28
         +  GetCalendarYear()       * 60 * 24 * 28 * 12;
}

const string sLASTREST = "lastrest";

// Sets the current GT as a global.
void SetRestRestriction()
{
    SetGlobalInt(sLASTREST, GetGT());
}

// Checks if 24+ GT-hours have passed.
int CheckRestRestriction()
{
    if (GetGT() - GetGlobalInt(sLASTREST) >= 60 * 24)
        return TRUE;

    return FALSE;
}
// kL_rest end.



and the calls in main()

    // kL_rest start ->
    if (!CheckRestRestriction())
    {
        return;
    }
    SetRestRestriction();
    // kL_rest end.




It also checks against a true 24 hour game-time interval (instead of a numerical day). The behavior re. "per click" rather than "per success" still applies. The start of the interval is set to the start of rest, so party has to wait only 16 hrs after rest completes successfully, or the full 24 hours if cancelled.


[note that on rare occasions persons have reported problems with Windows Paranoia settings vs. globals]

  • up
    50%
  • down
    50%
kevL's

this is an updated set of the previous functions, that will print the time remaining to all players.

( calls in main() are the same as above )


// kL_rest start ->
const int iREST_INTERVAL = 24;  // hrs. NOTE: Time seems to pass in 60-minute chunks on the OL.
                                // So precision is necessarily limited to 1-hour intervals.

// Prints the remaining interval before rest is allowed to all players in chat.
// - iInterval : minutes passed
void PrintRestriction(int iInterval)
{
    string sPrint = "<c=darkviolet>Time before party can rest :</c> ";

    iInterval = (iREST_INTERVAL * 60 - iInterval) / 60; // hours left

    sPrint += "<c=deeppink>" + IntToString(iInterval) + " hr";
    if (iInterval > 1) sPrint += "s";
    sPrint += ".</c>";

    object oPC = GetFirstPC(FALSE);
    while (GetIsObjectValid(oPC))
    {
        SendMessageToPC(oPC, sPrint);
        oPC = GetNextPC(FALSE);
    }
}

// Gets the current GT in minutes.
int GetGT()
{
    return  GetTimeMinute()
         +  GetTimeHour()           * 60
         + (GetCalendarDay() - 1)   * 60 * 24
         + (GetCalendarMonth() - 1) * 60 * 24 * 28
         +  GetCalendarYear()       * 60 * 24 * 28 * 12;
}

const string sLASTREST = "lastrest";

// Sets the current GT as a global.
void SetRestRestriction()
{
    SetGlobalInt(sLASTREST, GetGT());
}

// Checks if at least 'iREST_INTERVAL' GT-hours have passed.
int CheckRestRestriction()
{
    int iInterval = GetGT() - GetGlobalInt(sLASTREST);
    if (iInterval >= iREST_INTERVAL * 60)
    {
        return TRUE;
    }

    if (GetLastRestEventType() == REST_EVENTTYPE_REST_STARTED)
        PrintRestriction(iInterval);

    return FALSE;
}
// kL_rest end.

  • up
    50%
  • down
    50%
milspec

Thanks will test and post once complete. 

  • up
    50%
  • down
    50%
kevL's

doh, now i forgot to check if "current area" is the OverlandMap. I don't expect any real problem tho

  • up
    50%
  • down
    50%
milspec

Quick local test worked well. 

i am doing a real test on the remote MP server, which requires me re-learning staging files, etc. I will report back on done. 

  • up
    50%
  • down
    50%
kevL's

just a note (and good luck with staging, i don't know anything bout that)

So I've been re-working my rest scripts for the official campaigns, and still don't expect any noticeable problems (barring MP quirks) with above. however, in my version of the SoZ rest (also limited to 24hr rest on the OL), i understand better what they did and why. That led to more optimizations and -- what i noticed that might be relevant -- is that in the code above the rest-cancelled case won't fire correctly if the restriction causes an early return. (note: rest-finished never happens on the SoZ OL). This should not matter for the stock code; it could matter, though, if you want to put something inside case REST_EVENTTYPE_REST_CANCELLED.

That's unlikely, because anything other than case rest-started is pretty much irrelevant here. what the SoZ resting script does is immediately fire off a ClearAllActions(), then enters the rest-started block, which fires up the SoZ resting GUI. An altogether different script then executes via the resting GUI, which handles party resting via ActionRest() and ForceRest(). (it's not brilliant but hey it works). The point is that while that is going on, the ClearAllActions() call has fired the rest-event a second time, but now the rest-script would enter the rest-cancelled block.

Except that it won't since the restriction was set on the first fire (rest-started), so the second fire (rest-cancelled) quickly after that will exit early per CheckRestRestriction().

like i said, it's not an issue but i want to keep things straight (for myself at least.)

  • up
    50%
  • down
    50%
milspec

I tested the script with an external MP server (hosted on AWS), and connected two players in a party. The resting worked as expected. One party member was able to start the rest one time. If the other party member tried, or if the first party member tried again, or if it was canceled for any reason then all party members were required to wait for 24 hours before trying to rest again. It gives a nice message about how many hours they need to wait until they can rest again.

The only issue is what you mention, that canceling or any rest action that fails also resets the timer. In this case I think its really esily remedied by having the party go back to a town to rest in an Inn.

If you figure out a way to clear the timer on a rest cancel or rest interruption, let me know and I can help test.

I appreciate the fact you spent time on this - thanks very much for the help!!

milspec

 

  • up
    50%
  • down
    50%
kevL's

Okay. To set the restriction ONLY when rest [edit: usually] succeeds:


- first remove the line

    SetRestRestriction();

from 'k_mod_player_rest' main(). (Comment it out with "//" if ya prefer.) recompile.


- backup 'gui_rest' (or whatever you did with 'k_mod_player_rest' ...) (this file should be in the SoZ Campaign folder also)

- find the following code-block (line 341+ in the stock script)

    if(nState == 0) // rest
    {
            int bUseForceRest = GetGlobalInt(CAMPAIGN_SWITCH_REST_SYS_USE_FORCE_REST);
            DoWholePartyRest(oPC, bUseForceRest);
    }  
 


- and change it to this:

    if(nState == 0) // rest
    {
            // kL_rest start ->
            int iGt =  GetTimeMinute()
                    +  GetTimeHour()           * 60
                    + (GetCalendarDay() - 1)   * 60 * 24
                    + (GetCalendarMonth() - 1) * 60 * 24 * 28
                    +  GetCalendarYear()       * 60 * 24 * 28 * 12;
            SetGlobalInt("lastrest", iGt);
            // kL_rest end.

            int bUseForceRest = GetGlobalInt(CAMPAIGN_SWITCH_REST_SYS_USE_FORCE_REST);
            DoWholePartyRest(oPC, bUseForceRest);
    }    


Compile. (stage both, i guess)


The restriction should now be set ONLY when "Rest" is selected on the gui, and player clicks "Okay".

NOTE: if the party is too close to a *hostile* wandering monster, or *perhaps* if they get ambushed when trying to rest ... go to town or wait 24 hr. I'm leary of moving SetRestRestriction() even further down the pike, however, because i don't trust what i'm looking at in 'gui_rest' atm. (rest is successful when ambushed?? uh no. Etc)

 

 

update: I've come to the conclusion that the entire kL_rest sub-subsystem should be moved to 'gui_rest'. This would allow the party to "Wait" even when they can't "Rest", etc. (eg. If party can't rest yet, they could set up camp and "Wait" till enough time passes to allow "Rest".) Are you interested? it'd mean reverting 'k_mod_player_rest' to the stock files, and i'll do something (maybe even completely refactor) 'gui_rest' for SoZ.

Actually it'd be much quicker than that, for your purposes. But it means reverting 'k_mod_player_rest' to stock ... let me know what you think.

  • up
    50%
  • down
    50%
milspec

Yes I am OK with that. I have the base files backed up. I can test more in about 12 hours. (No rush.)

  • up
    50%
  • down
    50%
kevL's

k. In the meantime, found (at least) 2 bugs in the stock 'gui_rest' code ... a tentative refactor is in progress.

  • up
    50%
  • down
    50%
kevL's

here's 'gui_rest' with RestRestriction:

http://pastebin.myrror.net/3226


again, revert 'k_mod_player_rest' to stock.

I tested it in a clean player folder. behaves as expected on my end


Note that a stock install of Nwn2 has 2 versions of 'gui_rest' : 1 in the /Data folder (for MotB) which is overruled by the 1 in the SoZ Campaign folder (when playing SoZ).

My version conflates the two and it should work for anyone in their /Override.

Quirky things I noticed. when testing this in a clean player folder, I couldn't get an ambush to happen when resting*. But when compiling against my modified version of 'ginc_restsys' i could. And, regretably, ambushes don't seem to happen when Waiting in either case.

if you want to try compiling against my 'ginc_restsys' it's here:
http://pastebin.myrror.net/3227


( I don't have a clue what i did to it over the years to seemingly make ambushes work ... and it looks like i'll also want to enable triggering ambushes when Waiting sometime also, but I'm pretty sure that'd be in 'gui_rest' itself: ie, DoWholePartyWait() does NOT make a call to SinglePartyWMRestEncounterCheck() )

but it'd take some jiggery-pokery to ensure the ducks line up correctly (read: am not too concerned about it atm; except, it can be considered a cheat around the RestRestriction -- just do a couple of free Waits on the OL and bingo, Rest away...) hm

 

* this could be simply because I used the ASC-standalone to compile, and it likely compiled against 'ginc_restsys' in /Data instead of the one in the SoZ Campaign folder. (so it was looking for MotB-style wandering monsters instead of SoZ-style)

  • up
    50%
  • down
    50%
milspec

OK, tested. Works as expected.

I think a "Rest finished", "Rest interrupted" and "Finished waiting" confirmation message would be helpful. Right now it always says "XX hours pased", which is the same for a successful rest, failed rest or wait. One more line confirming what just happened would be a helpful debug (and useful in-game).

I compiled against your "ginc_restsys". As you said, I could wait any number of times without being ambushed. I did it ten times in a row, about 5 days passed. Nothing attacked. Up to you - any way to trouble shoot this or hook in the same ambush chance that resting uses?

At the moment I think I prefer the previous change, since it was a hard stop on anything more than once per day. It forced some interesting decisions, even if canceled. I agree with you, the endless waiting in this version seems like a bit of an "exploit".

UPDATE: I tested the new scripts on the MP server with two clients. The previous "one script" version only allowed the party leader to initiate resting or waiting. This new "three scripts" version allows any party member to try and rest or wait. I prefer the behavior of the previous version where it was only the party leader (the one who is able to interact with the world map) who can trigger the rest GUI.

milspec

 

  • up
    50%
  • down
    50%
kevL's
  • I could wait any number of times without being ambushed. I did it ten times in a row, about 5 days passed. Nothing attacked. Up to you - any way to trouble shoot this or hook in the same ambush chance that resting uses?

presently re-working 'ginc_restsys' ... <rant> they appear to have started with 'x2_inc_restsys' [nwn1], adapted it for OC+MotB, then taken that and adapted it for SoZ without removing the stuff for NwN1+OC+MotB, and threw in SingleParty rest subsys somewhere along the line. The result is that most of the ducks i'm trying to shoot are merely decoys.</rant>

the point: I won't try to insert an ambush-check for Wait, till i understand what needs to be done (exactly). It's getting close after these latest changes.


... I agree back with you that disallowing Wait to pass time forces some "interesting decisions". Here's a new version of 'gui_rest':
http://pastebin.myrror.net/3244

- if either Rest OR Wait is selected ( and Okay'd ), the restriction is reset. Note that party can always do a regular Wait when they want (but it will reset the restriction just like Rest does).
- if resting gets interrupted because the party is too close to a hostile, or because they got ambushed, the restriction is lifted so they can try another rest after the situation is corrected (ie, player gets the Okay-click refunded due to interrupt).


A note on Gui-scripts, like 'gui_rest' : the engine actually recompiles them on-the-fly, as the game is in progress. This can lead to bugs, since the internal compiler isn't so bright if you know what i mean. eg, Be careful with #include files; i've seen back-up copies take precedence over a proper #include. (There's also an issue with integer constants which are defined more than once - the internal compiler seems to just drop them and won't even compile the script - but i doubt we need to worry about it, 'cause it's pretty obvious if/when the script fails utterly...) Other irregularities are likely lurking.

 

  • I tested the new scripts on the MP server with two clients. The previous "one script" version only allowed the party leader to initiate resting or waiting. This new "three scripts" version allows any party member to try and rest or wait. I prefer the behavior of the previous version where it was only the party leader (the one who is able to interact with the world map) who can trigger the rest GUI.

that's odd /shrug. Here's a tidied version of 'k_mod_player_rest' that should allow ONLY the FactionLeader to invoke the resting Gui:
http://pastebin.myrror.net/3243
 

  • I think a "Rest finished", "Rest interrupted" and "Finished waiting" confirmation message would be helpful. Right now it always says "XX hours pased", which is the same for a successful rest, failed rest or wait. One more line confirming what just happened would be a helpful debug (and useful in-game).

done. It only appears in the chat window though.

  • up
    50%
  • down
    50%
milspec

Awesome, will test and update this thread.

  • up
    50%
  • down
    50%
kevL's

oops, looks like i sent the result-message exponentially ... to fix it, at the bottom of PlayerRestFinish(), replace the loop (lines 545-550) with

    DelayCommand(2.2f, SendMessageToPC(_oParty, sResult));

  • up
    50%
  • down
    50%
milspec

I tested it all on a MP server with two clients. It works as advertised. ;-) Only the party leader can start to rest, and if the party leadership is transferred then the new leader can start to rest. It gives a clear rest vs wait success message. Waiting resets the rest timer, so it can be used to advance the dusk / dawn cycle but not to skip the resting timer.

Nice work!

Up to you if you want to get the ambushes working for waiting from the rest GUI. I guess it adds some tension to the waiting cycle, rather that always resting safely even when in "imminent danger". I feel like all of the other changes make this 95% complete, the wait / ambush trigger is just a little icing on top. :-)

We play in a week, will report back again after that.

  • up
    50%
  • down
    50%
kevL's
  • Up to you if you want to get the ambushes working for waiting from the rest GUI. I guess it adds some tension to the waiting cycle, rather that always resting safely even when in "imminent danger". I feel like all of the other changes make this 95% complete, the wait / ambush trigger is just a little icing on top. :-)

a simple thing i did to my own scripts here is, on Wait, check if a hostile is nearby. (disallow the wait if true) I set the radius slightly larger than the check for on Rest ...

tbh, I'm not a fan of SoZ's encounter, encounter, encounter stuff. So the check-for-hostile is enough afaic.

here's a reworked and upgraded version of 'gui_rest' ... it's almost getting elegant ;)

http://pastebin.myrror.net/3260

( ... as long as it still works - a few minutes of testing says it's okay. )


tech: I removed the def'n of '_oPC' as GetPCSpeaker(). It's just OBJECT_SELF now. Because (1) why would a gui-script get called from a dialog, and (2) the very next line checks that no party-member is in conversation, so GetPCSpeaker() would be irrelevant anyway. I mean, it strikes me as *possible* but just barely.

in any case, fair warning
Note: the innkeeper at Samargol still let's my party rest just fine.

 

ps. back up your current copy of 'gui_rest'

  • up
    50%
  • down
    50%
kevL's

dang. The determination of _bIsMotB is bugged. It doesn't matter when playing SoZ, since it will be FALSE anyway. But i'll post the next version of 'gui_rest' at some point ........

It really doesn't matter, as long as the script is in the SoZ Campaign folder. (MotB can't access it there.)


[the MotB developers didn't tag their modules nicely, like the SoZ developers did]

  • up
    50%
  • down
    50%
milspec

Quick test looks good. Messing with some other server settings at the moment, will test more thoroughly in a day or two.

The game is at the end of this week.

 

  • up
    50%
  • down
    50%
kevL's

this should be 'gui_rest' final (barring tertiary bugs, but i don't expect any at this pt.)

http://pastebin.myrror.net/3265

(see changes at the top of script.)


good luck w/ the play, I'm currently tinkering w/ SP SoZ ,,,

  • up
    50%
  • down
    50%
milspec

OK, full test complete. Mostly good, one small issue below. For the record I have these installed on the server:

http://pastebin.myrror.net/3265

http://pastebin.myrror.net/3243

http://pastebin.myrror.net/3227

"Installed" means I paste them into a local copy of G_X2, overwrite the local file, re-compile via the toolset, and then upload the .nss and .ncs to the server. I also re-open and re-compile the files on the server, just to be sure. File location is in the main game folder (not the docs folder) in the X2 campaign folder. This method seems safter than the overrides folder. 

The party rest system seems to work. I get the new "enemy too close" message when a red outlined figure is close on the world map. I do not get it for blue (encounter) or white (patrols) figures. I can always rest normally in an Inn. I can only rest once per 24 hours on the world map. I can wait, but waiting resets the clock. If I try to rest in an "imminent danger" location I get interrupted in an encounter. After the encounter ends I can rest again. It seems stable over the 30 mins I have the patience to test. :-)

I ran into one small issue. The second client did not have the world map GUI displayed so could not access the rest menu, nor the player menu, nor see the clock.

I did not have a chance to troubleshoot any other related items. Both clients have the same overrides and UI folders. Any chance this is from a restriction on "rest ui" for the non-party leader? Note the party leadership transfers OK when the leader double clicks on another, but the UI on the second client still does not appear.

I will test by switching the primary and secondary clients and see if I can reproduce on a different system.

milspec

 

  • up
    50%
  • down
    50%
kevL's
  • The second client did not have the world map GUI displayed so could not access the rest menu, nor the player menu, nor see the clock.

Is that the only ui missing? Ie, are the fancy SoZ OL border and special party-bar (on the right, vertically aligned portraits) okay?

- the clock/rest/options ui is defined by 'nx2_ol_menu.xml', in:

<install>\Campaigns\Neverwinter Nights 2 Campaign_X2\UI

perhaps it got corrupted ( .xml is sensitive to typos.. )

I'd also guess that gui's are client-side files. (scripted callbacks are not however)

  • "Installed" means I paste them into a local copy of G_X2, overwrite the local file, re-compile via the toolset,

i'm sorta surprised you didn't back up the three campaign files locally, then copy-in and test them from there. because,

  • [on the server] File location is in the main game folder (not the docs folder) in the X2 campaign folder.

... locally, the stock 'ginc_restsys' (in the campaign folder) might be overriding a 'ginc_restsys' in the module folder (depending on whether your local copy of G_X2 is hooked up to the Campaign). etc ....


Plus, here's a bunch of tech-stuff i thought through (have been adulterating my personal SoZ quite thoroughly ...)

maybe it has a clue somehow


---
not sure if it's related to the Restriction (etc.)

when did the OL-gui start to disappear? In any case, here's a trace:

- the OL-gui is defined by files in <install>/Campaigns/Neverwinter Nights 2 Campaign_X2/

nx2_ol_*.xml

where * is frame, menu, partybar, playermenu.

note that playermenu_popup has been altered (by SoZ) from stock in the same folder, and (in its parent folder) the gui's for characterscreen, contextmenu, and creatureexamine have also been altered from stock.

- entering OL Map:

campaign file 'nx2_ol_client_enter' (area "g00_overland" OnClientEnter script) calls ActivateOLMapUI() in 'ginc_overland' (also in the X2 Campaign folder, watch out for duplicates in /override... as with any of these scripts - Kaedrin's PrC Pack eg, modifies pretty much every script mentioned here). ActivateOLMapUI() is called for *each entering PC if it is currently controlled by a player*. This closes the standard player-ui (party-bar, hotbars, mode-bar, playermenu, and action-queue) and displays the SoZ OL Map elements: the fancy SoZ OL Map frame, the special party-bar ui, and the special player-menu ui (w/ clock, options, rest).



- exiting OL Map:

campaign file 'ka_olmap_exit' (area "g00_overland" OnExit script) calls ExitOverlandMap() in 'ginc_overland' (also in the X2 Campaign folder, watch out for duplicates in /override...). ExitOverlandMap() calls DeactivateOLMapUI() on *all* pc-faction.

DeactivateOLMapUI() is also in 'ginc_overland'. This closes the fancy SoZ OL Map frame, the special party-bar ui, and the special player-menu ui (w/ clock, options, rest). It also re-activates the ordinary UI elements (party-bar, hotbars, mode-bar, playermenu, and action-queue).



Our modified OnPlayerRest script 'k_mod_player_rest' (which ensures that only the FactionLeader can initiate rest) should not fire until a player clicks Rest -- so if the OL-gui fails to load prior to that, this can likely be ruled out as a suspect.

The gui-script 'gui_rest' should only fire when the FactionLeader initiates rest. So if the OnPlayerRest script can be ruled out (because the GUI has already failed), so can this.

Hence, as far as the rest-restriction scripts, that leaves 'ginc_restsys'. It's an #include file. If a callback from a Gui, aka a gui-script ( starts with "gui_" ), calls a function from 'ginc_restsys' and the latter is failing for any reason the script will fail to compile on-the-fly. You could try renaming 'ginc_restsys.nss' to 'ginc_restsys_BYPASSED.nss' (on the server) to see if the issue gets corrected [ EDIT: if so, don't forget to stage an SoZ 'ginc_restsys' from your local X2 Campaign folder, else the engine will (try to) compile gui-scripts against the stock (non-SoZ) 'ginc_restsys' in the <install>/data folder ].


note: the OnModuleLoad event 'k_mod_load' also makes calls to 'ginc_restsys':
    WMSet2DAFileName(WM_DEFAULT_2DA_FILE); // "restsys_wm_table"
    WMBuild2DACache();


(but I don't believe the result is even used in SoZ, offhand. That is, SoZ uses it's own rest-tables, not the default)

  • up
    50%
  • down
    50%
milspec

Quick reply. 

Took your implied advice, restored X2 campaign folder to default. Copied modified scripts into campaign folder. Did not open in editor. Did not try to recompile anything on the server. That keeps all other files with original dates. Runs well.

Yes the second client issue also missing fancy borders. Did not test yet, will in 12 hours. Game in 36 hours, racing the clock. ;-)

Thanks for detailed notes, will dig through when I test second client. 

At this stage my primary concerns are not w resting. Biggest unknown is restoring from saves to ensure we can advance. And what happens when we move from G to the next module - hope the server knows what to load.

milspec

 

 

 

  • up
    50%
  • down
    50%
kevL's

So it sounds like you've broken-out all the modules to folders.

"F_X2.mod" -> "<docs>/Neverwinter Nights 2/modules/F_X2/"
etc.

I did this too, with the added quirk of renaming the module-folders ... "SoZ_F_X2_kL", eg.

( note: changes made inside a module-folder will not have an affect after a module is entered and saved -- since the runtime state of each module has to be stored in the /Saves -- that is, the module will get zipped-up from the files that are in the server's temporary/working directory. )

After backing up the X2 Campaign folder, I opened the campaign-editor, cleared all modules from the X2 Campaign, associated the module-folders instead, then opened each module-folder in the toolset and clicked "Use this module in this campaign" (or whatever it is..). Save the module (like it says to do) and move on to the next module.

If you didn't change the names of the modules, i'd guess that should be enough. (but here i'll have to go through everything searching for variables and calls to SaveRosterLoadModule() ...)

/ymmv. ( am guessing you'd have to stage at least Campaign.cam to the server )


----

re. Gui

(no rush )

It sounds like ... you're saying, the OL gui is simply not present on a client-machine (ie, not displayed, at least) as soon as the party transitions to the OL. But, neither is the standard gui -- does the standard gui re-appear after leaving the OL?

- try, on the faulty client, exiting and re-entering the game while party is on the OL Map. Can you make the OL-gui appear at all (for that client)

- that is, if only that machine fails to show a gui it's local to that machine ... (i'd say)

  • up
    50%
  • down
    50%
milspec

No. Did not expand module into folder. Copied modified scripts into (main game directory) X2 campaign folder, overwriting the originals. Module is untouched, all other campaign files untouched. Seems to work fine. 

  • up
    50%
  • down
    50%
kevL's

ok

  • up
    50%
  • down
    50%
milspec

I was able to fix the world map GUI not appearing on the second client by deleting all of the various module / campaign / save files in the second client's docs/NWN2 folders. Upon the next test the world map GUI showed, the second client was able to initiate rest (when they were the party leader), etc.

  • up
    50%
  • down
    50%
kevL's

cool. Sounds like you guys are good to go ?

  • up
    50%
  • down
    50%
milspec

Our first session went well. The resting script worked as expected, and we were able to save and reload our progress and confirmed quest progress and world state were saved  

We played for about 4 hours, which is pretty good considering we are all Dads over 40. :-)

I am currently researching how to use NWNX4 to host a save file. We used it to host the module, but loading a save is different from loading a module. 

Thanks for all of the help with the rest scripts!

milspec

  • up
    50%
  • down
    50%