Aller au contenu

Photo

How bad are Pseudo HB's?


  • Veuillez vous connecter pour répondre
33 réponses à ce sujet

#26
henesua

henesua
  • Members
  • 3 863 messages

ShaDoOoW wrote...
There is still a question that if you need heartbeat for each area why do you dont use modules's heatbeat and then do stuff on each area.


I think it depends on what you are doing. I do use a mod heartbeat and loop through PCs but I limit this to PC specific functions. There are many features that one can potentially add specifically for PCs in the PC Loop and I think there is a danger of putting too much in it. Also if you have many players, I would think that strapping everything into the PC loop could scale poorly.

Regarding weather, I think it would be better handled in area heartbeats as opposed to the mod heartbeat if the majority of areas are not exterior. That way the script is where you expect it to be, and you are not trying to do everything in the mod heartbeat's PC Loop. You also do not need to add this particular script to every area, only the ones that have weather which you want to control.

In my view, efficiency is only one part of the equation. More important to me is writing scripts that a team can easily maintain. After inheriting a very old persistent world and digging into the scripts, I could see first hand how establishing a clean and rational design to your scripts is probably the most important element of scripting for that world. So for that reason I don't see why (if you are working on a group project which the OP seems to be doing) you'd stick weather in a PC loop when you could use area focused scripts.

Now it is very likely that pseudos in the onenter for an area script are the best solution - because its relatively well organized, effcient, and probably scales well with increased players - but until I run into a problem with area heartbeats I prefer to use the events as they were intended to be used.

#27
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

henesua wrote...

ShaDoOoW wrote...
There is still a question that if you need heartbeat for each area why do you dont use modules's heatbeat and then do stuff on each area.


I think it depends on what you are doing. I do use a mod heartbeat and loop through PCs but I limit this to PC specific functions. There are many features that one can potentially add specifically for PCs in the PC Loop and I think there is a danger of putting too much in it. Also if you have many players, I would think that strapping everything into the PC loop could scale poorly.

The only danger of putting too much in it is one of inefficiency. There's no question that individual area heartbeats are worse in this regard. The only plausible reason for wanting to split them up is modularity, and there, as you say, pseudos are likely a better choice, if slightly more complicated than simply using the event bioware provided.

Now it is very likely that pseudos in the onenter for an area script are the best solution - because its relatively well organized, effcient, and probably scales well with increased players - but until I run into a problem with area heartbeats I prefer to use the events as they were intended to be used.

I assume that you'r simply reiterating your point about simplicity of use, especially in a team, in a different way. That's a totally valid goal, but the way you phrase it here, as adhering to the intents of the designers, is a terrible justification for doing anything. By way of example, they presumably intended builders to use the database package they put in the program, and it's atrociously bad. You have to remember they were on a production schedule, and hadn't had anything like the amount of time the community has had to tinker. There are usually better ways of doing things than the way they originally coded them, even when simplicity is of primary concern.

Funky

#28
henesua

henesua
  • Members
  • 3 863 messages

FunkySwerve wrote...
The only danger of putting too much in it is one of inefficiency. There's no question that individual area heartbeats are worse in this regard. The only plausible reason for wanting to split them up is modularity, and there, as you say, pseudos are likely a better choice, if slightly more complicated than simply using the event bioware provided.

Modularity is one reason, and the one I am using , but its not the only plausible reason. Isn't there a danger of TMI?

I assume that you'r simply reiterating your point about simplicity of use, especially in a team, in a different way.


No, I was simply bringing the subject back for the OP, so as to keep this on topic. And add that I also agree with you that the onEnter pseudo heartbeat can be the best solution for this application.

That's a totally valid goal, but the way you phrase it here, as adhering to the intents of the designers, is a terrible justification for doing anything. By way of example, they presumably intended builders to use the database package they put in the program, and it's atrociously bad. You have to remember they were on a production schedule, and hadn't had anything like the amount of time the community has had to tinker. There are usually better ways of doing things than the way they originally coded them, even when simplicity is of primary concern.


I disagree. Arguments like yours keep some terrible game dev tools alive when they should die. (No, I do not think NWN should die. I am referring in particular to a commercial grade engine.) As a rule I use an engine as it was intended. If I need to make a few exceptions here and there that is alright, but when I find that more often than not I am working against the engine to achieve what i want, I will use something else.

I typically use Unity. NWN is for me an opportunity to avoid making custom game assets (textures, models, sounds etc...) and instead focus on protyping a role playing game. I've never made one before, and don't have the ability or time to make one from scratch on my own. After NWN, I'll go back and give it a shot. I've already learned the lessons by remaking a PW, and now I'm on to a single player thing. Its been lots of fun. NWN is good fun, but its maddening the amount of hard coded limitations one has to deal with. Pseudo Heartbeats being in my view just another example of a work around.

Modifié par henesua, 25 juin 2011 - 06:51 .


#29
DM_Vecna

DM_Vecna
  • Members
  • 280 messages
After looking I am not using a module heartbeat script nor a area. I use onEnter to save location and (onExit with psudo) to track posulation, clean up areas, etc. But I am curious what others find that they use onmodheartbeat for? Maybe there are some good suggestions for some fun stuff I have not looked at yet. :) Weather makes sense but I am not sure what else.

#30
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

henesua wrote...

FunkySwerve wrote...
The only danger of putting too much in it is one of inefficiency. There's no question that individual area heartbeats are worse in this regard. The only plausible reason for wanting to split them up is modularity, and there, as you say, pseudos are likely a better choice, if slightly more complicated than simply using the event bioware provided.

Modularity is one reason, and the one I am using , but its not the only plausible reason. Isn't there a danger of TMI?

Fair enough, especially if you're going single player, as you say below. Generally, though, this shouldn't be an issue, and you can always avoid it by AssignCommanding problem segments of code to start the TMI count over. If you're dealing with multiplayer NWNX, you can just use the TMI plugin to up the limit temporarily, as we do on modload. It's worth remembering that the TMI limit is only there to prevent those with less experience from shooting themselves in the foot, so to speak (as well as to alert the rest of us to a loop problem). The devs just neglected to provide an off switch for the rest of us, though the community has seen to that since (via one of your 'terrible game dev tools' :P ).

I disagree. Arguments like yours keep some terrible game dev tools alive when they should die. (No, I do not think NWN should die. I am referring in particular to a commercial grade engine.) As a rule I use an engine as it was intended. If I need to make a few exceptions here and there that is alright, but when I find that more often than not I am working against the engine to achieve what i want, I will use something else.

Game dev tools like what, exactly? I'm only pointing out that religiously using certain techniques because they were put there by the designers will lead to some very bad results, given the options available. I don't see how that in any way supports the use of 'terrible game dev tools'. Good ones, sure.

NWN is good fun, but its maddening the amount of hard coded limitations one has to deal with. Pseudo Heartbeats being in my view just another example of a work around.

Recursive functions aren't exactly some newfangled thing... In any event, it is PRECISELY these types of limitations that call for end user workarounds. I'm having difficulty understanding why you malign willingness to use them when available as keeping terrible dev tools alive, and then complain about limitations militating for the use of such workarounds in the next breath.

Anyway, we should probably continue this in pms, to prevent further sidetrackig of the thread. I'll put future responses there, unless they have  more direct bearing on the topic.

Funky

#31
henesua

henesua
  • Members
  • 3 863 messages

FunkySwerve wrote...
Anyway, we should probably continue this in pms, to prevent further sidetrackig of the thread. I'll put future responses there, unless they have  more direct bearing on the topic.


If the Original Poster or others asks me to desist I will. Otherwise, writing only for your benefit doesn't seem worth it, as the back and forth is likely to benefit someone else as well. If it is not worth it for you to post here, thats fine, but I'd rather not go back and forth in PMs.

Generally, though, this [TMI] shouldn't be an issue, and you can always avoid it by AssignCommanding problem segments of code to start the TMI count over.


thats useful. thanks. didn't realize.

Game dev tools like what, exactly?


Torque is the perfect example. I was not speaking of NWN.

I'm only pointing out that religiously using certain techniques because they were put there by the designers will lead to some very bad results, given the options available. I don't see how that in any way supports the use of 'terrible game dev tools'. Good ones, sure.


You misunderstood me. Nowhere am I laying down hard and fast rules nor am I calling NWN a terrible game dev tool. Rather I am stating a preference to use the tool as intended, and judge it on those merits. I also said that making a few exceptions here and there is fine. If the engine does not function as intended in most cases however, its good to be aware of that as NWN is not the only usable game dev tool anymore.

Also accepting the engine's limitations, because of one's attachment to it is just as much of a problem especially if you let it influence the way you think about and write code. Its a good way to develop bad habits. Perhaps thats not a problem for you. But it is for me. And since NWN isn't the only engine I use, I'm not going to let it define the way I work and think. I don't think anyone else should either if they want to keep making games.

#32
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<peeking through the aether>

henesua wrote...

... as the back and forth is likely to benefit someone else as well. 


It does :-)

<goes back to taking notes silently>

#33
Taino

Taino
  • Members
  • 139 messages

GhostOfGod wrote...

I was planning on putting a blank HB script in all areas as I'm building them just in case I want to put something in later. Now I'm not sure if I should. How much of a resource difference are we talking here?

I would like to know this as well...

Also what about the Module's Event Heartbeat? The less the better or make sure it is clean and smooth sailing.

#34
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

Taino wrote...

GhostOfGod wrote...

I was planning on putting a blank HB script in all areas as I'm building them just in case I want to put something in later. Now I'm not sure if I should. How much of a resource difference are we talking here?

I would like to know this as well...

No one in their right mind is going to have an answer to that question based on prior experience, because it's a consumately foolhardy thing to do - no gain in ease of use or modularity, and added cost in cpus. If you want to put somehthing in later, use the Module heartbeat if you want it in all areas, or an area-enter pseudo if you just want it in one (or just the module hearbeat, if you're uncomfortable with pseudos - having multiple area heartbeats is...silly, to put it politely).

Also what about the Module's Event Heartbeat? The less the better or make sure it is clean and smooth sailing.

Both. We actually loop pcs multiple times in ours because the loop is fast, and we wanted to keep the different parts distinct (or 'clean' as you put it). A single loop would be slightly faster, but the difference is negligible. This is our mod heartbeat, and it's very quick, despite how it looks. Everything area-specific requiring heartbeat behavior is done via pseudoheartbeats launched from our universal area-enter script, which Executes an area-specific script if the proper var is set on the area, for modularity and ease of use.

#include "hg_inc"

#include "fky_chat_inc"
#include "hg_client_inc"

#include "ac_effect_inc"
#include "ac_itemprop_inc"


void SendResetBroadcast (string sMessage, int bTell, object oMessenger) {
    sMessage = "<cþ þ>" + sMessage + "</c>";

    object oPC = GetFirstPC();

    while (GetIsObjectValid(oPC)) {
        SendChatLogMessage(oPC, sMessage, oMessenger, (bTell ? 4 : 5));
        oPC = GetNextPC();
    }
}


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));

        SQLExecDirect("UPDATE server_list SET srv_utime = " + IntToString(nUptime) + ", srv_memory = " +
            IntToString(nMemory) + ", srv_players = " + IntToString(nPlayers) + " WHERE srv_id = '" + sServer + "'");

        SQLExecDirect("SELECT COUNT(*) FROM user_list WHERE u_active > 0");
        if (SQLFetch() == SQL_SUCCESS)
            SetLocalInt(oMod, "GlobalPlayers", StringToInt(SQLGetData(1)));

        SQLExecDirect("SELECT COUNT(*) FROM user_list WHERE u_active > 0 AND u_server_name REGEXP '" + GetStringLeft(sServer, 1) + ".[1-8]'");
        if (SQLFetch() == SQL_SUCCESS) {
            int nHubPlayers = StringToInt(SQLGetData(1));

            SetLocalInt(oMod, "HubPlayers", nHubPlayers);

            if (GetStringLeft(sServer, 1) != "1" && GetStringRight(sServer, 1) == "1")
                SetNumberOfPlayers(nHubPlayers);
        }
    }


    /* check for server messages if available */
    if (nMessages > 0) {
        int nGuild;
        string sMessage;
        string sSQL = "SELECT um_id, um_guild, um_text FROM user_messages WHERE um_recipient = '*" + sServer + "'";

        SQLExecDirect(sSQL);

        sSQL = "";

        while (SQLFetch() != SQL_ERROR) {
            if (sSQL == "")
                sSQL = "um_id = " + SQLGetData(1);
            else
                sSQL += " OR um_id = " + SQLGetData(1);

            nGuild   = StringToInt(SQLGetData(2));
            sMessage = SQLDecodeSpecialChars(SQLGetData(3));

            if (nGuild == -2) {
                object oPC = GetFirstPC();

                while (GetIsObjectValid(oPC)) {
                    if (!(GetPCFilter(oPC, PCFILTER_CHANNELS) & 1))
                        SendChatLogMessage(oPC, sMessage, oMes);

                    oPC = GetNextPC();
                }
            } else if (nGuild == -3) {
                object oPC = GetFirstPC();

                while (GetIsObjectValid(oPC)) {
                    if (!(GetPCFilter(oPC, PCFILTER_CHANNELS) & 2))
                        SendChatLogMessage(oPC, sMessage, oMes);

                    oPC = GetNextPC();
                }
            } else if (nGuild == -4) {
                object oPC = GetFirstPC();

                while (GetIsObjectValid(oPC)) {
                    if (!(GetPCFilter(oPC, PCFILTER_CHANNELS) & 4))
                        SendChatLogMessage(oPC, sMessage, oMes);

                    oPC = GetNextPC();
                }
            } else if (nGuild < 0) {
                SendMessageToDMDMs(sMessage);
                SendMessageToPCDMs(sMessage);
                SendMessageToDMAdmins(sMessage);
                SendMessageToPCAdmins(sMessage);
            } else if (nGuild > 0) {
                object oPC = GetFirstPC();

                while (GetIsObjectValid(oPC)) {
                    if (GetLocalInt(oPC, "Guild") == nGuild && !(GetPCFilter(oPC, PCFILTER_GUILD) & 1))
                        SendChatLogMessage(oPC, sMessage, oMes);

                    oPC = GetNextPC();
                }
            } else
                AssignCommand(oMod, SpeakString(sMessage, TALKVOLUME_SHOUT));
        }

        if (sSQL != "")
            SQLExecDirect("DELETE FROM user_messages WHERE " + sSQL);
    }


    /* scan players for inter-server messages every minute; also check for auto-reset */
    if (nUptime >= GetLocalInt(oMod, "LastMessageCheck") + 60) {
        SetLocalInt(oMod, "LastMessageCheck", nUptime);

        if (nMemory > GetLocalInt(oMod, "resetmemory") && !GetLocalInt(oMod, "resetforce")) {
            SetLocalInt(oMod, "resetforce", 1);

            if (GetLocalInt(oMod, "resetuptime") > nUptime)
                SetLocalInt(oMod, "resetuptime", nUptime - 1);
        }


        /* update PCs in the user list */
        UpdateAllClients(sServer, sBootTime, oMes);


        /* check for automatic reset */
        int nResetUptime = GetLocalInt(oMod, "resetuptime");
        if (nResetUptime > 0 && nUptime > nResetUptime) {

            if (!GetIsObjectValid(GetFirstPC()))
                SetLocalString(oMod, "NWNX!RESETPLUGIN!SHUTDOWN", "1");

            if (nUptime > nResetUptime + 900) {
                SendResetBroadcast("SERVER RESET IN 10 SECONDS - SERVER REBOOT IS NOW COMMITTED - CANCELLATION " +
                    "IS NO LONGER POSSIBLE - PLEASE STAY OUT OF BANK CHESTS - HAVE A NICE DAY!", 1, oMes);
                DelayCommand(10.0, SetLocalString(oMod, "NWNX!RESETPLUGIN!SHUTDOWN", "1"));
            } else {
                int bTell = 0;
                int nSeconds = (nResetUptime + 900) - nUptime;
                string sMinutes = IntToString((nSeconds / 60) + 1);

                if (sMinutes == "1" || sMinutes == "5" || sMinutes == "10" || sMinutes == "15")
                    bTell = 1;

                if (GetLocalInt(oMod, "resetforce"))
                    SendResetBroadcast("AUTOMATIC SERVER RESET IN " + sMinutes + " MINUTE" +
                        (sMinutes != "1" ? "S" : "") + " - THIS CANNOT BE ABORTED DUE TO A LOW MEMORY CONDITION", bTell, oMes);
                else
                    SendResetBroadcast("AUTOMATIC SERVER RESET IN " + sMinutes + " MINUTE" +
                        (sMinutes != "1" ? "S" : "") + " - USE !delayreset TO DELAY THE RESET", bTell, oMes);
            }
        }
    }


    /* scan players for epic buffs every minute */
    if (nUptime >= GetLocalInt(oMod, "LastImmunityBuffCheck") + 60) {
        int i;
        object oCreator, oArea, oItem, oPC = GetFirstPC();

        while (GetIsObjectValid(oPC)) {
            oArea = GetArea(oPC);

            if (GetIsDead(oPC)           ||
                !GetIsObjectValid(oArea) ||
                GetHasEffectOfType(EFFECT_TYPE_CONFUSED, oPC)) {
                oPC = GetNextPC();
                continue;
            }


            if (GetHasSpellEffect(HGSPELL_EPIC_AEGIS, oPC)) {
                oCreator = GetLocalObject(oPC, "EpicCreator_Aegis");

                if (!GetIsObjectValid(oCreator) || GetArea(oCreator) != oArea || !GetFactionEqual(oCreator, oPC)) {
                    if (GetLocalInt(oPC, "EpicCollapse_Aegis") > 1) {
                        RemoveEffectsFromSpell(HGSPELL_EPIC_AEGIS, oPC);
                        FloatingTextStringOnCreature(
                            "* The Aegis spell on you collapses without the support of its caster! *",
                            oPC, FALSE);
                    } else
                        AddLocalInt(oPC, "EpicCollapse_Aegis", 1);
                } else
                    DeleteLocalInt(oPC, "EpicCollapse_Aegis");
            }


            if (GetHasSpellEffect(HGSPELL_EPIC_CHANT_OF_WARDING, oPC)) {
                oCreator = GetLocalObject(oPC, "EpicCreator_Chant");

                if (!GetIsObjectValid(oCreator) || GetArea(oCreator) != oArea || !GetFactionEqual(oCreator, oPC)) {
                    if (GetLocalInt(oPC, "EpicCollapse_Chant") > 1) {
                        RemoveEffectsFromSpell(HGSPELL_EPIC_CHANT_OF_WARDING, oPC);
                        FloatingTextStringOnCreature(
                            "* The Chant of Warding spell on you collapses without the support of its caster! *",
                            oPC, FALSE);
                    } else
                        AddLocalInt(oPC, "EpicCollapse_Chant", 1);
                } else
                    DeleteLocalInt(oPC, "EpicCollapse_Chant");
            }


            if (GetHasSpellEffect(HGSPELL_EPIC_ELEMENTAL_SHUNT, oPC)) {
                oCreator = GetLocalObject(oPC, "EpicCreator_Shunt");

                if (!GetIsObjectValid(oCreator) || GetArea(oCreator) != oArea || !GetFactionEqual(oCreator, oPC)) {
                    if (GetLocalInt(oPC, "EpicCollapse_Shunt") > 1) {
                        RemoveEffectsFromSpell(HGSPELL_EPIC_ELEMENTAL_SHUNT, oPC);
                        FloatingTextStringOnCreature(
                            "* The Elemental Shunt spell on you collapses without the support of its caster! *",
                            oPC, FALSE);
                    } else
                        AddLocalInt(oPC, "EpicCollapse_Shunt", 1);
                } else
                    DeleteLocalInt(oPC, "EpicCollapse_Shunt");
            }


            if (GetHasSpellEffect(HGSPELL_EPIC_SHROUD_OF_NATURE, oPC)) {
                oCreator = GetLocalObject(oPC, "EpicCreator_Shroud");

                if (!GetIsObjectValid(oCreator) || GetArea(oCreator) != oArea || !GetFactionEqual(oCreator, oPC)) {
                    if (GetLocalInt(oPC, "EpicCollapse_Shroud") > 1) {
                        RemoveEffectsFromSpell(HGSPELL_EPIC_SHROUD_OF_NATURE, oPC);
                        FloatingTextStringOnCreature(
                            "* The Shroud of Nature spell on you collapses without the support of its caster! *",
                            oPC, FALSE);
                    } else
                        AddLocalInt(oPC, "EpicCollapse_Shroud", 1);
                } else
                    DeleteLocalInt(oPC, "EpicCollapse_Shroud");
            }


            oPC = GetNextPC();
        }

        SetLocalInt(oMod, "LastImmunityBuffCheck", (nUptime - 5) + Random(11));
    }


    if (nUptime >= GetLocalInt(oMod, "LastEquipmentBuffCheck") + 60) {
        int i;
        object oCreator, oArea, oItem, oPC = GetFirstPC();

        while (GetIsObjectValid(oPC)) {
            oArea = GetArea(oPC);

            if (GetIsDead(oPC)                                 ||
                !GetIsObjectValid(oArea)                       ||
                GetHasEffectOfType(EFFECT_TYPE_POLYMORPH, oPC) ||
                GetHasEffectOfType(EFFECT_TYPE_CONFUSED, oPC)) {
                oPC = GetNextPC();
                continue;
            }

            if ((GetLevelByclass(class_TYPE_BLACKGUARD, oPC) + GetLevelByclass(class_TYPE_PALADIN, oPC)) >= 40) {
                if (GetKnowsFeat(FEAT_SMITE_EVIL, oPC))
                    IncrementRemainingFeatUses(oPC, FEAT_SMITE_EVIL);
                if (GetKnowsFeat(FEAT_SMITE_GOOD, oPC))
                    IncrementRemainingFeatUses(oPC, FEAT_SMITE_GOOD);
            }

            if (GetHasSpellEffect(HGSPELL_EPIC_GIRDING_OF_THE_FAITHFUL, oPC)) {
                oCreator = GetLocalObject(oPC, "EpicCreator_Gird");

                if (!GetIsObjectValid(oCreator) || GetArea(oCreator) != oArea || !GetFactionEqual(oCreator, oPC)) {
                    if (GetLocalInt(oPC, "EpicCollapse_Gird") > 1) {
                        RemoveEffectsFromSpell(HGSPELL_EPIC_GIRDING_OF_THE_FAITHFUL, oPC);
                        DelayCommand(0.1, VoidRemoveAllItemPropertiesFromSpell(HGSPELL_EPIC_GIRDING_OF_THE_FAITHFUL, oPC));

                        FloatingTextStringOnCreature(
                            "* The Girding of the Faithful spell on you collapses without the support of its caster! *",
                            oPC, FALSE);
                    } else
                        AddLocalInt(oPC, "EpicCollapse_Gird", 1);
                } else
                    DeleteLocalInt(oPC, "EpicCollapse_Gird");
            }


            if (GetHasSpellEffect(HGSPELL_EPIC_INSTRUMENTS_OF_FAITH, oPC)) {
                oCreator = GetLocalObject(oPC, "EpicCreator_Instr");

                if (!GetIsObjectValid(oCreator) || GetArea(oCreator) != oArea || !GetFactionEqual(oCreator, oPC)) {
                    if (GetLocalInt(oPC, "EpicCollapse_Instr") > 1) {
                        RemoveEffectsFromSpell(HGSPELL_EPIC_INSTRUMENTS_OF_FAITH, oPC);
                        DelayCommand(0.1, VoidRemoveAllItemPropertiesFromSpell(HGSPELL_EPIC_INSTRUMENTS_OF_FAITH, oPC));

                        FloatingTextStringOnCreature(
                            "* The Instruments of Faith spell on you collapses without the support of its caster! *",
                            oPC, FALSE);
                    } else
                        AddLocalInt(oPC, "EpicCollapse_Instr", 1);
                } else
                    DeleteLocalInt(oPC, "EpicCollapse_Instr");
            }


            oPC = GetNextPC();
        }

        SetLocalInt(oMod, "LastEquipmentBuffCheck", (nUptime - 5) + Random(11));
    }
}

Funky