Author Topic: Any idea on how to save PC's and avoid aborting barter as much as possible (on a PW)?  (Read 640 times)

Legacy_SuperFly_2000

  • Hero Member
  • *****
  • Posts: 1292
  • Karma: +0/-0


               ...or at least not breaking it too much...

I know I could make it so chars are saved every 10 minutes for example...

...but how to do it to get the least problems?

(Edit: Changed the title a bit.)
               
               

               


                     Modifié par SuperFly_2000, 02 novembre 2010 - 03:13 .
                     
                  


            

Legacy_TheSpiritedLass

  • Hero Member
  • *****
  • Posts: 1118
  • Karma: +0/-0


               What do you mean by breaking barter?

I have a set of save scripts that I've been using for years and they work great.  To be honest, I don't know if they have ever been specifically tested with barter.  I'm interested to know how doing a player file save breaks barter.  What a "cool" set of variables to have to have all at once to break stuff.  (Yes I know, my geekiness is showing.)

http://nwvault.ign.c...d=20023&id=3430

-- Mistress, sipping on the first cup of coffee for the day.
               
               

               
            

Legacy_SuperFly_2000

  • Hero Member
  • *****
  • Posts: 1292
  • Karma: +0/-0


               Well.."breaking" is probably not the right word here...but barter is closed down when saving all the characters. That is normal NWN behavior and that is how it should be actually.



When I come to think of it I don't think you can go around that easily...but smart things is to:



- Not save characters too often!

- Save characters when they are probably not bartering...like when they press rest or similar.

- Maybe even in some other ways or situations that I didn't think of...

               
               

               
            

Legacy_kalbaern

  • Hero Member
  • *****
  • Posts: 1531
  • Karma: +0/-0


               Saving will break bartering. I'd suggest a two fold save method (which I use myself). First, set your auto saving feature to between 40 - 60 mins. 10 mins between auto saves seems pretty fast unless the PW suffers from regular crashes, in which case, resolving why it crashes is the best option. Secondly, you can add a save feature to either the rest or crafting convos so your players can save after important events manually. You could also force a save when exiting dungeons or turning in a quest reward for just the PCs doing so.
               
               

               


                     Modifié par kalbaern, 02 novembre 2010 - 03:11 .
                     
                  


            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0


               You want it to break barter. I vaguely recall that this is an antiexploit that prevents duping, though I could be mistaken.



Funky
               
               

               
            

Legacy_SuperFly_2000

  • Hero Member
  • *****
  • Posts: 1292
  • Karma: +0/-0


               Yeah...I know...thats why I changed the topic title because it was a bit misleading...
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0


               If you wanted to save the characters more oftem then when the rest and advoid the x amount of time thing, You could set it up to save the characters every time they moved to a new area.

OnAreaEnter Or OnAreaExit.
               
               

               
            

Legacy_SuperFly_2000

  • Hero Member
  • *****
  • Posts: 1292
  • Karma: +0/-0


               Yeah...thats kind of nifty...still I don't know if I dare to not have any timed save of the character at all.



Just realised that there might be different ways to do it (?).



There seems to be a server setting for saving characters...wonder if I should use that or make a script that runs OnEnter of the PC and then like every 10 minutes or so....
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0


               You can just give them a saving pen and let them save whenever they like. If you do this, though, you'd also want an onunacquire save with an int check to make sure it only fires every second or so (to avoid multiple saves when a bag is dropped, for example). This is the combination we finally settled on, since anything with ExportAll causes a large number of problems (bartering, for example, and shifted characters, being another that comes prominently to mind. Allowing player-controlled saves is far and away the most popular option with players, but it opens up dupe exploits without the aforementioned unacquire. We also save on rest, because players inevitably forget to use the saving pen.

Here's the saving pen code, which I believe originally came from a Vault script, but is now basically all original code:



#include "fky_chat_const"
#include "x2_inc_switches"

#include "hg_inc"
#include "hg_hardcore_inc"

//new functions added as part of shifter-friendly save

//Tells whether the PC is a shifted shifter or druid - returns TRUE if they are shifted
int GetIsShifted(object oPC) {
    effect eEffect;
    int nShifted = FALSE;

    if (GetLevelByclass(class_TYPE_SHIFTER, oPC) > 0 || GetLevelByclass(class_TYPE_DRUID, oPC) > 0) {
        eEffect = GetFirstEffect(oPC);

        while (GetIsEffectValid(eEffect)) {
            if (GetEffectType(eEffect) == EFFECT_TYPE_POLYMORPH) {
                nShifted = TRUE;
                break;
            }

            eEffect = GetNextEffect(oPC);
        }
    }

    return nShifted;
}

//export a single character unless shifted
void SafeExportSingle(object oPlayer) {
    if (GetArea(oPlayer) != OBJECT_INVALID && GetIsShifted(oPlayer) == FALSE) {
        ExportSingleCharacter(oPlayer);
    }
}

//export em all unless shifted, with a .5 second delay between each
void SafeExportAll() {
    object oPlayer = GetFirstPC();
    float fDelayPerPC = 0.0;

    while (GetIsObjectValid(oPlayer)) {
        DelayCommand(fDelayPerPC, SafeExportSingle(oPlayer));
        fDelayPerPC = fDelayPerPC + 0.5;
        oPlayer = GetNextPC();
    }
}

void main() {
    int nEvent = GetUserDefinedItemEventNumber();
    object oPC;
    object oItem;
    string sName, sPCName;
    int nResult = X2_EXECUTE_SCRIPT_END;

    if (nEvent == X2_ITEM_EVENT_ACTIVATE) {
            oPC = GetItemActivator();
            oItem = GetItemActivated();

            ExportSingleCharacter(oPC);
            if (GetIsHardcore(oPC))
                UpdateHardcoreStats(oPC);

            SendMessageToPC(oPC, "Your character was saved.");
            sName = GetName(oPC);
            sPCName = GetPCPlayerName(oPC);
            WriteTimestampedLogEntry("Saving Pen used by player: " + sPCName + ", character: " + sName + ".");
            SendMessageToAllDMs(COLOR_LT_BLUE2 + "Saving Pen used by player: " + sPCName + ", character: " + sName + "." +
                COLOR_END);

    }

    SetExecutedScriptReturnValue(nResult);
}


Obviously not all will apply to your server, but that's the gist. Here's the unacquire code:




#include "hg_inc"
#include "playerhide_inc"
#include "x2_inc_itemprop"

#include "ac_itemreq_inc"
#include "ac_itemprop_inc"
#include "fky_chat_const"

#include "hg_hardcore_inc"

void main() {
    object oItem = GetModuleItemLost();
    object oLoser = GetModuleItemLostBy();
    object oArea = GetArea(oItem);
    object oPCArea = GetArea(oLoser);
    string sResRef = GetResRef(oItem);
    int nItType = GetBaseItemType(oItem);
    object oPossessor = GetItemPossessor(oItem);

    if (GetLocalInt(oLoser, "DebugItemEvents")) {

        if (!GetIsObjectValid(oPossessor))
            oPossessor = GetArea(oItem);

        SendMessageToPC(oLoser, COLOR_WHITE + "Item Event: Unacquire" +
            ", Loser: " + GetName(oLoser) + " (" + GetObjectString(oLoser) +
            "), Possessor: " + GetName(oPossessor) + " (" + GetObjectString(oPossessor) +
            "), Item: " + GetName(oItem) + " (" + GetObjectString(oItem) +
            ") [x" + IntToString(GetItemStackSize(oItem)) + "]" + COLOR_END);
    }

    /* the object/area/possessor valid checks here are to ensure that
     * barter doesn't break with onunacquire saving */
    if (nItType != BASE_ITEM_POTIONS          &&
        nItType != BASE_ITEM_ENCHANTED_POTION &&
        (!GetIsObjectValid(oItem) ||
         GetIsObjectValid(oArea)  ||
         GetIsObjectValid(GetItemPossessor(oItem)))) {

        ForceDelayedSave(oLoser);
    }

    /* covers all the epic spells */
    string sRes3 = GetStringLeft(sResRef, 3);
    if (FindSubString(" es_ ps_ qc_ aa_ ca_ fa_ ra_ sa_ ", " " + sRes3 + " ") >= 0 ||
        sResRef == "waterbreath"    ||
        sResRef == "ringoffirewalk" ||
        sResRef == "ringofpasswall" ||
        sResRef == "ringoflevitation") {

        if (sResRef != "ca_brd_stillsnd") {
            DestroyObject(oItem);
            FloatingTextStringOnCreature("Forcing undroppable items out of inventory destroys them.", oLoser, FALSE);
        }
    }

    /* make sure bind-on-use items can't be transferred in bags */
    if (GetItemCursedFlag(oItem) && sResRef == "bountifulbeaker") {
        DestroyObject(oItem);
        FloatingTextStringOnCreature("Forcing undroppable items out of inventory destroys them.", oLoser, FALSE);
    }

    if (GetItemIsStackable(oItem) && GetItemHasItemProperty(oItem, ITEM_PROPERTY_INVIS_ADDITIONAL)) {
        itemproperty ip;

        for (ip = GetFirstItemProperty(oItem); GetIsItemPropertyValid(ip); ip = GetNextItemProperty(oItem)) {
            if (GetItemPropertyType(ip) == ITEM_PROPERTY_INVIS_ADDITIONAL &&
                GetItemPropertyCostTableValue(ip) == IP_CONST_ADDITIONAL_UNDROPPABLE) {

                DestroyObject(oItem);
                FloatingTextStringOnCreature("Forcing undroppable items out of inventory destroys them.", oLoser, FALSE);
                break;
            }
        }
    }


    /*  ---- 'INVISIBLE' HELMS ---- //scripts in onequip, onunequip, onunacquire */
    if (nItType == BASE_ITEM_HELMET) {
        if (oItem == GetLocalObject(oLoser, "LastHelm")) {
            object oHide = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oLoser);

            if (!GetIsObjectValid(oHide))
                oHide = GetItemPossessedBy(oLoser, "playerhide");

            if (GetIsObjectValid(oHide))
                RemoveInvisibleHelmProperties(oHide);

            DeleteLocalObject(oLoser, "LastHelm");
            DeleteLocalString(oLoser, "LastHelm");

            SetLocalInt(oLoser, "MidPlayerHideVerification", 1);
            DelayCommand(0.1, VerifyPlayerHide(oLoser));
        }
    }

    RemoveItemPropertiesOfDuration(DURATION_TYPE_TEMPORARY, oItem, -(ITEM_PROPERTY_LIGHT + 1));

    DoHardCoreUnacquireTag(oLoser, oPossessor, oItem, oArea, oPCArea);
}



And here's the critical ForceDelayedSave function:



void ForceDelayedSave (object oPC, float fDelay=2.0) {
    if (!GetIsPC(oPC) || GetIsDM(oPC))
        return;

    int nUptime = GetLocalInt(GetModule(), "uptime");
    int nDelayedSave = GetLocalInt(oPC, "DelayedSave");

    if (nDelayedSave < nUptime) {
        SetLocalInt(oPC, "DelayedSave", nUptime);
        DelayCommand(fDelay, ForceSave(oPC, TRUE));
    } else if (nDelayedSave == nUptime + 1) {
        SetLocalInt(oPC, "DelayedSave", nUptime + 2);
        DelayCommand(10.0, ForceSave(oPC));
    }
}


The uptime var is just incremented by 6 every module heartbeat (well, really we retrieve the Unix timestamp for greater accuracy, since lin servers tend to fire heartbeats around every 5.2 seconds, but as with horseshoes, hand grenades, and nuclear warfare, close is good enough here).



void main() {
    object oMod = GetModule();
    object oMes = GetMessenger();
    int nUptime = GetLocalInt(oMod, "uptime");
    int nMemory = GetProcessMemoryUsage();
    int nMessages = 0, nPlayers = 0;
    string sServer = GetLocalString(oMod, "ServerNumber");
    string sBootTime = IntToString(GetLocalInt(oMod, "boottime"));

    {
        object oPC;

        for (oPC = GetFirstPC(); GetIsObjectValid(oPC); oPC = GetNextPC()) {
            nPlayers++;
            RecalculateMovementRate(oPC);
            RecalculateDexModifier(oPC);

            int nAlarm = GetLocalInt(oPC, "AlarmUptime");
            if (nAlarm > 0 && nAlarm <= nUptime) {
                DeleteLocalInt(oPC, "AlarmUptime");
                SendChatLogMessage(oPC, C_PINK + "[Alarm] " + GetLocalString(oPC, "AlarmMessage") + C_END, oMes, 4);
            }
        }

        SetLocalInt(oMod, "ServerPlayers", nPlayers);
    }

    SQLExecDirect("SELECT UNIX_TIMESTAMP() - " + sBootTime + ", UTC_TIMESTAMP(), UNIX_TIMESTAMP(), " +
        "COUNT(*) FROM user_messages WHERE um_recipient = '*" + sServer + "'");

    if (SQLFetch() == SQL_SUCCESS) {
        nUptime = StringToInt(SQLGetData(1));
        nMessages = StringToInt(SQLGetData(4));

        SetLocalInt(oMod, "uptime", nUptime);
        SetLocalInt(oMod, "realtime", StringToInt(SQLGetData(3)));
        SetLocalString(oMod, "utctime", SQLGetData(2));
*snip*



Obviously, no solution is perfect, at least in this case - it depends heavily on what your players want, and how complex you're willing to make the code. Sorry for the codebomb nature of the above - lmk if you have questions.

Funky
               
               

               
            

Legacy_SuperFly_2000

  • Hero Member
  • *****
  • Posts: 1292
  • Karma: +0/-0


               Thanks but should be enough with 1-3 lines of code to do this.



Personally I don't like the idea of a saving pen because that forces players to do something about this and think about this.



Anyway...I feel I know pretty much what I need to know about this.



The only remaining question is pros/cons with using either a simple script on the OnClientEnter or use the server setting (in the ini file).