Author Topic: Types of scripts to avoid  (Read 680 times)

Legacy_EzRemake

  • Full Member
  • ***
  • Posts: 220
  • Karma: +0/-0
Types of scripts to avoid
« on: December 16, 2013, 10:05:12 pm »


               There are still quite a few projects being developed, which is awesome! I was thinking maybe we could start a thread about server performance in regards to scripting. Things like, script types to avoid (very complicated heartbeats), certain ways to avoid writing scripts etc.. so that we all can prevent performance loss, lag, and at worst, crashes.

As of right now the only thing I know to be careful of using is heartbeat scripts, and to be honest, I don't even use any as of right now in my module.

What I'm curious of in particular is how intense case scripting is, and what about nested case scripting?
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Types of scripts to avoid
« Reply #1 on: December 16, 2013, 10:32:56 pm »


               Cases are functionally identical to ifs, ditto nesting, at least in nwscript. Most optimized way is binary breaks, but you can only have 7 levels of nesting in nwscript, if memory serves. Not really a critical aspect of efficient scripting, though, compared to other issues, which I covered in my lag-busting tutorial for the lexicon, here:
Lag Busting

Funky
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Types of scripts to avoid
« Reply #2 on: December 17, 2013, 12:09:38 am »


               The 7 levels is in max depth for a script calling another script.    I do not think there is a max depth for a case since they are in effect just if statements.
               
               

               
            

Legacy_EzRemake

  • Full Member
  • ***
  • Posts: 220
  • Karma: +0/-0
Types of scripts to avoid
« Reply #3 on: December 17, 2013, 12:15:30 am »


               I noticed the one point in your lexicon tutorial stated not to use hard placed NPCs, and to have everything spawned in via trigger or encounter, and I was curious how much of an impact this actually has?

I've been using Sir Elric's Simple Creature Respawn for years now, and haven't noticed any such lag with having hundreds of hard placed creatures, and 90% of said creatures are wandering with ambient animations. I've been keeping an eye on my server's CPU and RAM usage and neither climb at all. CPU usage is 0% for the most part.

I didn't know about separating groups into different factions, but I'm going to go through every area and re-faction different groups (this is going to be a nightmare).
               
               

               


                     Modifié par EzRemake, 17 décembre 2013 - 12:31 .
                     
                  


            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Types of scripts to avoid
« Reply #4 on: December 17, 2013, 02:17:15 am »


               Those are listed in order of importance, based on the performance hikes we got from doing them. Factions was a huge performance boost, though you can get some of that via creative editing of creature silent shouts, if you know what you're doing or spend the time to figure it out. NPCs also have a very noticeable impact, but you'll mostly see it proportionate to the number of people in the mod, because the NPCs will be firing listening scripts in response to silent shouts - otherwise, they're set to occupy very little of the engine's time when PCs are not in the same area.

For the refactioning, you can restrict it to area groupings. We have many hundreds of areas in the mod (well over 600 the last time I counted), but only about 80 custom factions, I think - haven't counted lately. By way of example, we have one custom faction for all our abyss areas, which fall into 5 different 'planes', each having 9-12 areas. Ditto our hell faction, which comprises 28 areas. It all boils down to area groupings. When I retrofitted our mod, I had to make a couple duplicate critters, just fyi, for places where there were the same creature spawning in in different areas that did not fit well into one 'group'. The amount of overlap you have is going to dictate how many factions you have.

Word of warning - if you're not having lag issues, it's not worth the time to do this. Remember, computers today are a lot more powerful than they were when NWN dropped.

Funky
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Types of scripts to avoid
« Reply #5 on: December 17, 2013, 08:34:27 am »


               

EzRemake wrote...

I noticed the one point in your lexicon tutorial stated not to use hard placed NPCs, and to have everything spawned in via trigger or encounter, and I was curious how much of an impact this actually has?


Not that big as one would though.

In singleplayer you dont have to worry about this at all.
In multiplayer it depends on how good is your server machine, how often you want to restart the nwserver.

It also depends on the gamestyle, if you are making a "HCRP" module where majoritz of the content is low lvl, where monsters spawn usually in low numbers and slowly its not a problematic as if you were creating a high-lvl epic action module where usually you spawn around 500 monsters per one dungeon/20minutes. Even then, if you have a despawn in place normally you won't encounter any issues, Arkhalia epic-action server I was formerly admin is able to run 3days without reset without noticeable lags. But its true that I put quite a lot of work to make this possible.

Some of my tips based on working with big epic-action module:
- despawning is a must, sometimes players just running trough a dungeon especially if they can do this and don't have to kill anything or if they don't get any experiences from monsters
- in such modules, stores can create big lags when players overfills them(thousand+ sold items), this has been biggest issue till I joined DM team and server had to be previously restarted every day just because of this (certain unofficial patch solves this issue via module switch)
- if you have standing plot neutral npcs who just talk and not moving, not fighting, I suggest to remove all their scripts
- remove the OnSpellCastAt script on monsters that are often being massed by spells, this script causes ultimate lags, certain unofficial patch partially solves this but when you have 50+ monsters on one spot its not going to help you, best is to avoid spawning so many creatures at once at all (sometimes builders doing this with those tiny spiders or vermins from cep) if its possible
- suggest to disable stacking of AOEs, AOEs are biggest source of lags especially if you cast such spell into pack of 50monsters, and especially if you cast more than one of these, when I played as a druid with a tactics of massing entire area with entangle, grease, stonehold, sov,sov,sov,sov and offensive spells it created such big lags that each sov lasted not 10 rounds but 30:devil: so if you can, try restrict AOEs to only one instance per player which seems to me as best performance and balance solution, afaik HG has it this way
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Types of scripts to avoid
« Reply #6 on: December 17, 2013, 04:31:15 pm »


               While I agree with most of what Shadow wrote, I disagree with this:

ShaDoOoW wrote...

EzRemake wrote...

I noticed the one point in your lexicon tutorial stated not to use hard placed NPCs, and to have everything spawned in via trigger or encounter, and I was curious how much of an impact this actually has?


Not that big as one would though.

In singleplayer you dont have to worry about this at all.
In multiplayer it depends on how good is your server machine, how often you want to restart the nwserver.


He's right about singleplayer. In multiplayer, however, we had issues even on a good machine, and switching to spawning them in and out made the second-most noticeable impact on performance (again, per the ordering in that tutorial). Also, it has little to do with how often you want to restart nwserver, as there are no memory leaks associated with placed NPCs, unless you're doing something wrong in your code (at least so far as I'm aware).

For the store issue, we solved it by making separate sell and buy stores, with the players able to select which they want to access via conversatino. This solves the issue because the lag generates when large numbers of sold items are checked against the store inventory. If the store is empty to start, you don't get the massive lag spike when items are sorted post-sale (and I do mean massive - it was capable of bringing down a high-end server machine's mod instance, with only a couple hundred items sold).

Also, as to this:

suggest to disable stacking of AOEs, AOEs are biggest source of lags
especially if you cast such spell into pack of 50monsters, and
especially if you cast more than one of these, when I played as a druid
with a tactics of massing entire area with entangle, grease, stonehold,
sov,sov,sov,sov and offensive spells it created such big

Actually, AOEs are not inherently the source of much lag - it's their graphics. We swapped most of ours out to alternate graphics, and use them like crazy with no ill effects. Cloud of Bewilderment is among the worst offenders.

Funky
               
               

               


                     Modifié par FunkySwerve, 17 décembre 2013 - 04:33 .
                     
                  


            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Types of scripts to avoid
« Reply #7 on: December 17, 2013, 05:31:54 pm »


               

FunkySwerve wrote...

Also, it has little to do with how often you want to restart nwserver, as there are no memory leaks associated with placed NPCs, unless you're doing something wrong in your code (at least so far as I'm aware).


I'm reasonably sure he was referring to the fact that if you place monsters manually then they don't respawn, hence you need to restart so the module actually has monsters.

Another interesting point about placement versus spawning: if I look at a module that uses spawning with some triggers and waypoints, the size is 3.26 MB with effectively 10 areas.  If I remove the waypoints and spawning triggers and instead manually place 33 creatures that spawns in just ONE of the areas, the size becomes 3.36 MB.  Which basically means something like every 300 creatures takes up 1 MB.  So if you had 500 areas with 30 creatures each, you're looking at an extra 50 MB of size.

The funny thing is I would have sworn that not placing creatures saved even more memory than that -- I seem to recall it cutting module size into something like 1/2 or even 1/3 in some experiments I was running a year or two ago.  Possibly because that old module had creatures with many items each so that probably took up more memory per creature, maybe.  I specifically never gave creatures more than one item (a weapon) in the module I was just testing.
               
               

               


                     Modifié par MagicalMaster, 17 décembre 2013 - 06:30 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Types of scripts to avoid
« Reply #8 on: December 17, 2013, 05:55:43 pm »


               

MagicalMaster wrote...

I'm reasonably sure he was referring to the fact that if you place monsters manually then they don't respawn, hence you need to restart so the module actually has monsters.

What I meant is that if you intent to restart server every 6 hours, which I found as a standard on many PWs, you don't have to worry much about efficiency. Such servers usually havent encountered any lag troubles yet at all and they never did any of this prevention. From the question of OP I assume he is either beginning building or he have a module already and read many topics here and there about lag issues, scripting efficiency etc. which is in his case all redundant info as scripters often arguing about something that has absolutely no impact on a performance in a long-term run anyway.
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Types of scripts to avoid
« Reply #9 on: December 17, 2013, 06:05:13 pm »


               

MagicalMaster wrote...

The funny thing is I would have sworn that not placing creatures saved even more memory than that -- I seem to recall it cutting module size into something like 1/2 or even 1/3 in some experiments I was running a year or two ago.  Possibly because that old module had creatures with many items each so that probably took up more memory per creature, maybe.  I specifically never gave creatures more than one item (a weapon) in the module I was just testing.


I'm with Shad on this one too. You can wrap yourself in knots trying to optimize and accomplish very little. In this case, you also run the risk of causing other problems. To use HG as an example, we use a custom encounter system, spawning in npc enemies semi-randomly, based on preconfigured variables. As a part of that system, we use a TON of waypoints, to emulate creature behavior. So, while we may be saving some on module size in doing that, we're also adding a lot more objects to the module that have to be looped through every time you use GetObjectByTag. While GOBT is lightning-fast, we're still  likely hurting performance more than we're helping it with those encounters (the point of which is flexibility of spawning, not efficiency, by the way).

The main thing to concern yourself with is not to get in the habit of doing things that add a lot of overhead needlessly, as they can quickly add up. If you rely on that guide I linked, and deviate from it sparingly, you should be ok. If, however, you're not having much lag at the moment, it's just not worth pretzling yourself to try to further optimize.

Funky
               
               

               


                     Modifié par FunkySwerve, 17 décembre 2013 - 06:07 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Types of scripts to avoid
« Reply #10 on: December 17, 2013, 07:25:08 pm »


               

FunkySwerve wrote...

Actually, AOEs are not inherently the source of much lag - it's their graphics. We swapped most of ours out to alternate graphics, and use them like crazy with no ill effects. Cloud of Bewilderment is among the worst offenders.

Funky

hmm interesting but not really believe that because I was doing one test recently. I was in that time yet suspecting the SoV lasts longer than 10rounds, so I made a test where I casted one SoV on one target and counted rounds - 10. Then I casted three SoVs and it was suddenly 15rounds each (for anyone who will repeat this test - it might be totally fine in your module, it hapened in a specific module). Imo SoV is quite light visual effect and it was usead only against single target. But will test this assumption and try to disably any vfxes from AOEs if it helps.
               
               

               


                     Modifié par ShaDoOoW, 17 décembre 2013 - 07:26 .
                     
                  


            

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Types of scripts to avoid
« Reply #11 on: December 17, 2013, 08:16:00 pm »


               

FunkySwerve wrote...

So, while we may be saving some on module size in doing that, we're also adding a lot more objects to the module that have to be looped through every time you use GetObjectByTag. While GOBT is lightning-fast, we're still  likely hurting performance more than we're helping it with those encounters (the point of which is flexibility of spawning, not efficiency, by the way).


If you have one spawn waypoint per creature, then when the module loads isn't that the same as having a creature at each of those positions in terms of object count?  Sure, when you spawn some creatures then you technically have more objects -- but don't you have an equal amount until that point?

The tons of waypoints for specific behaviors is a different matter.

Also, how significant is the differenc between GOBT versus GNOBT?  If you have 10 areas with 10 objects each, then does GOBT take 10 times as long (linear with no significant constant cost) or would it take like twice as long (due to some constant cost of the function itself)?

Finally, I thought you mentioned in a conversation that you were trying to bring module size down -- or was that solely a result of decreasing module resources (the 16k limit thing -- combining scripts and such) and the memory size decrease merely a fortunate byproduct?
               
               

               
            

Legacy_EzRemake

  • Full Member
  • ***
  • Posts: 220
  • Karma: +0/-0
Types of scripts to avoid
« Reply #12 on: December 17, 2013, 08:18:09 pm »


               In my situation, manually placed NPCs do respawn. When I ran Evernight a few years ago it had about 300 areas, all filled with manually placed NPCs, using the same respawn script, and there were no issues with lag. That's why I am confused with this subject.

Even in the discussion threads for Sir Elric's Simple Creature Respawn you can see people talking about how no lag was added when using hard placed NPCs.

I suppose it has to do with many factors. Even now with the current module I'm building, with 58 areas filled with ambient roaming NPCs, my server's CPU usage sleeps at 0% when empty, and climbs only a percent or two with two players bashing away at monsters (the most I've tested is 2, since I'm still in an alpha stage).

I general though, I really do not anticipate having many concurrent players at any given time, so I'm not really sure how relevant most of this is anyways.

(Also, when using script profiling in nwnplayer.ini, how can you tell how much time is too much for a script to be running)
               
               

               
            

Legacy__Guile

  • Hero Member
  • *****
  • Posts: 1308
  • Karma: +0/-0
Types of scripts to avoid
« Reply #13 on: January 04, 2014, 12:53:27 am »


               All NPCs need to be spawned in & de-spawned either by triggers or Area events...

There is a lot you can do to reduce lag, I don't use heartbeat scripts at all anymore, rather area event scripting is far more my focus, so when a PC leaves an area, their AOEs just go poof, along with any other NPCs or script running objects...

The big one is not to use DelayCommand() whenever possible...
(Though it's ok for short delays, long delays are a big no no..)
Also, don't use GetObjectByTag() rather use a waypoint to reference the object, using GetWaypoint() & GetNearestObjectByTag(oWay, "name");

There are a lot of ways to reduce lag, the main thing is to be extra careful about using any kind of combat scripting, especially items that use OnHit effects (that can seriously spike your server with 10 - 60+ people firing 2 - 6 scripts a round)...  (10 monks dual welding kamas with on hit cast spell can get insane fast!)
               
               

               


                     Modifié par _Guile, 04 janvier 2014 - 12:54 .
                     
                  


            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Types of scripts to avoid
« Reply #14 on: January 04, 2014, 01:56:37 am »


               
Quote
_Guile wrote...
The big one is not to use DelayCommand() whenever possible...
(Though it's ok for short delays, long delays are a big no no..)

While we do generally avoid unnecessary DC's, this isn't remotely close to a 'big one'. Only people who are hyper-attentive to effeciency should even pause to consider it, and there are generally few good alternatives to DCs, when used appropriately.

When used inappropriately, it's not generally the DC that's the problem, but the approach to the event triggering that's the issue. Long DCs generally fall into the 'inefficient scripts' catchall in the tutorial, because there's usually a much better way to code them, often one involving timestamps.

By way of example, we don't respawn most things in our areas by delay anymore, but by checking a timestamp on area enter to see if the area should be respawned yet. You get no respawning until it's actually necessary (and, as a side effect, no DC running).
Quote

Also, don't use GetObjectByTag() rather use a waypoint to reference the object, using GetWaypoint() & GetNearestObjectByTag(oWay, "name");

This is flatly wrong. GOBT is much, much faster than GetWaypoint combined with GNOBT. While I used to think this was the case, tere was a thread on it some time back that caused me to run some tests. Even in modules with zillions of objects, like mine, there's no competition. This is because GOBT is much better optimized in the engine. Run some profiling and you'll see.
Quote

There are a lot of ways to reduce lag, the main thing is to be extra careful about using any kind of combat scripting, especially items that use OnHit effects (that can seriously spike your server with 10 - 60+ people firing 2 - 6 scripts a round)...  (10 monks dual welding kamas with on hit cast spell can get insane fast!)

This is an old piece of misinformation which became more prevalent around the time when sonic onhit effects were added. We use onhits like crazy, and they don't cause problems. What DOES cause 'lag', is weapon onhit vfx, for people with cards that can't hanlde it - most especially the sonic effect, but the others as well. This lag is entirely graphical, per the definitions in my tutorial, and has nothing to do with the onhit scripts themselves.

For reference, this is the onhit script we use on all players, as well as any monsters who have abilities we want to fire from onhit. It fires like crazy, with no ill effects. In point of fact, it was specifically engineered to allow as many simultaneous onhits as earthly possible. Every single onhit buff (like flame weapon) we have, uses it, and we have a bunch of custom ones, like acid weapon. It's not uncommon to have 5 or 6 players with 3 or 4 weapon buffs apiece swinging this script 5 times each per round, along with god knows how many critters.

Quote

#include "hg_inc"

#include "ac_spell_inc"
#include "ac_dispel_inc"
#include "ac_effect_inc"
#include "ac_itemprop_inc"
#include "ac_itemenh_inc"
#include "ac_check_inc"

#include "x2_inc_itemprop"

/*
 * OnHitDamages format:
 *   TYPE,DICE,SIDES,BONUS[,RACEMASK]
 *
 * OnHitEffects format:
 *   DYNEFFECT[|CHECK[|CHECK...]]
 *
 * DYNEFFECT format:
 *   LINKEFFECT[;LINKEFFECT...][:SECONDS[+RANDOM]][:E|S]][/VFX][#SPELLID]
 *
 * LINKEFFECT format:
 *   EFFECTID[,PARAM...]
 *
 * CHECK format:
 *   CHECKTYPE:PARAM[:PARAM...][/NAME]
 *
 * CHECKTYPES
 *   1 - percent chance (param is percentage chance)
 *   2 - saving throw (params are type, dc, random dc, subtype, feedback)
 *   3 - ability check (params are ability, dc, random dc, feedback, auto)
 *   4 - skill check (params are skill, dc, random dc, feedback, auto)
 *   5 - favored enemy check (param is a favored enemy mask)
 *   6 - spell resistance check (param is spell penetration)
 *
 *   & - last check was successful (and)
 *   ^ - last check failed (exclusive or)
 *
 * OnHitBreach (integer)
 *   if > 0 will breach that many spells
 *   if < 0 will act as a dispel of level = abs(OnHitBreach)
 *
 */

void SetItemPropertiesFromSpellDurations (object oItem, int nSpellId, float fDur) {
    object oCreator = GenCreator();

    itemproperty ip = GetFirstItemProperty(oItem);

    while (GetIsItemPropertyValid(ip)) {
        if (GetItemPropertyDurationType(ip) == DURATION_TYPE_TEMPORARY &&
            GetItemPropertySpellId(ip) == nSpellId) {

            RemoveItemProperty(oItem, ip);
            AssignCommand(oCreator, DelayCommand(0.1, AddItemProperty(DURATION_TYPE_TEMPORARY, ip, oItem, fDur)));
        }

        ip = GetNextItemProperty(oItem);
    }
}


void DegradeOnHitProperties (object oWeapon, itemproperty ip, int nSpellId, int nDegrade, object oCreator) {
    if (!GetIsObjectValid(oWeapon) || !GetIsItemPropertyValid(ip))
        return;

    if (GetIsObjectValid(oCreator)                &&
        GetArea(oCreator) == GetArea(OBJECT_SELF) &&
        GetFactionEqual(oCreator, OBJECT_SELF)    &&
        !GetPCOption(oCreator, PCOPTION_NOAUTOREBUFF)) {

        int nHasSpell = GetHasSpell(nSpellId, oCreator);

        if (nHasSpell > 0) {
            DecrementRemainingSpellUses(oCreator, nSpellId);

            if (nHasSpell > GetHasSpell(nSpellId, oCreator)) {
                FloatingTextStringOnCreature(C_MED_ORANGE + "* Auto-recasting " +
                    SFGetSpellName(nSpellId) + " for " + GetName(OBJECT_SELF) + "! *" + C_END,
                    oCreator, FALSE);

                SetLocalInt(oWeapon, "OnHitCreatorRests_" + IntToString(nSpellId), GetLocalInt(oCreator, "Rests"));
                return;
            }

            FloatingTextStringOnCreature(C_MED_ORANGE + "* Unable to auto-recast " +
                SFGetSpellName(nSpellId) + " for " + GetName(OBJECT_SELF) + "! *" + C_END,
                oCreator, FALSE);
        } else {
            FloatingTextStringOnCreature(C_MED_ORANGE + "* No casts left to refresh " +
                SFGetSpellName(nSpellId) + " for " + GetName(OBJECT_SELF) + "! *" + C_END,
                oCreator, FALSE);
        }
    }

    float fDur = GetItemPropertyDurationRemaining(ip) / 2.0;

    SetItemPropertiesFromSpellDurations(oWeapon, nSpellId, fDur);

    if (GetIsObjectValid(oCreator))
        SetLocalInt(oWeapon, "OnHitCreatorRests_" + IntToString(nSpellId), GetLocalInt(oCreator, "Rests"));

    string sBy = GetLocalString(oWeapon, "OnHitCreatorName_" + IntToString(nSpellId));

    if (sBy != "")
        sBy = "(by " + sBy + ") ";

    FloatingTextStringOnCreature(C_MED_ORANGE + "* The " + SFGetSpellName(nSpellId) + " effect " +
        sBy + "on your " + GetName(oWeapon) + " has degraded! *" + C_END, OBJECT_SELF, FALSE);
}


void main () {
    object oCaster = OBJECT_SELF;
    object oWeapon = GetSpellCastItem();
    object oTarget = GetSpellTargetObject();

    int nBase = GetBaseItemType(oWeapon);

    if (nBase > 0 && GetLocalInt(oWeapon, "Unlimited") == nBase) {
        string sCreator = GetLocalString(oWeapon, "UnlimitedCreator");

        if (sCreator != "") {
            object oCreator = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCaster);

            if (GetResRef(oCreator) != sCreator) {
                DestroyObject(oWeapon);
                return;
            }
        }


        int nIQCR = GetLocalInt(oWeapon, "IQCR");

        if ((nIQCR > 0 && GetQuasiclass(oCaster) != nIQCR) ||
            (nIQCR < 0 && GetQuasiclass(oCaster) != 0)) {

            DestroyObject(oWeapon);
            return;
        }

        SetItemStackSize(oWeapon, 99);
    }

    //no onhits for summons other than unlim ammo
    if (GetLocalInt(oWeapon, "SummonBlockOnHit"))
        return;


    int nVis = GetLocalInt(oWeapon, "OnHitVFX");

    string sDamages = GetLocalString(oWeapon, "OnHitDamages");
    string sEffects = GetLocalString(oWeapon, "OnHitEffects");
    string sScripts = GetLocalString(oWeapon, "OnHitScripts");

    int nBreach = GetLocalInt(oWeapon, "OnHitBreach");


    string sProp, sSpellId;
    int nPropType, nCostValue, nSpellId, nDegrade, bDegrade = -1;
    itemproperty ip = GetFirstItemProperty(oWeapon);

    /* check temporary item properties; if they are +1 to a damage type or
     * an enhancement bonus, their associated spell may  have set onhit
     * properties on the weapon */
    while (GetIsItemPropertyValid(ip)) {
        if (GetItemPropertyDurationType(ip) == DURATION_TYPE_TEMPORARY) {
            if (bDegrade < 0) {
                int nUptime = GetLocalInt(GetModule(), "uptime");

                if (nUptime > GetLocalInt(oWeapon, "OnHitDegradeNext")) {
                    bDegrade = 1;
                    SetLocalInt(oWeapon, "OnHitDegradeNext", nUptime + 60);
                } else
                    bDegrade = 0;
            }

            nPropType = GetItemPropertyType(ip);
            nCostValue = GetItemPropertyCostTableValue(ip);

            if (nPropType == ITEM_PROPERTY_DAMAGE_BONUS ||
                nPropType == ITEM_PROPERTY_KEEN         ||
                nPropType == ITEM_PROPERTY_ENHANCEMENT_BONUS) {

                if ((nSpellId = GetItemPropertySpellId(ip)) > 0) {
                    sSpellId = IntToString(nSpellId);

                    if (nPropType == ITEM_PROPERTY_DAMAGE_BONUS && nCostValue == IP_CONST_DAMAGEBONUS_1) {
                        if ((sProp = GetLocalString(oWeapon, "OnHitDamages_" + sSpellId)) != "") {
                            if (sDamages != "")
                                sDamages += " " + sProp;
                            else
                                sDamages = sProp;
                        }

                        if ((sProp = GetLocalString(oWeapon, "OnHitEffects_" + sSpellId)) != "") {
                            if (sEffects != "")
                                sEffects += " " + sProp;
                            else
                                sEffects = sProp;
                        }

                        if ((sProp = GetLocalString(oWeapon, "OnHitScripts_" + sSpellId)) != "") {
                            if (sScripts != "")
                                sScripts += " " + sProp;
                            else
                                sScripts = sProp;
                        }

                        if (nVis == 0)
                            nVis = GetLocalInt(oWeapon, "OnHitVFX_" + sSpellId);

                        nCostValue = GetLocalInt(oWeapon, "OnHitBreach_" + sSpellId);
                        if (nBreach == 0 || nCostValue > nBreach)
                            nBreach = nCostValue;
                    }

                    /* check for property degradation once per minute; require 2 minutes for
                     * the first degrade and 1 minute for each subsequent one. */
                    if (bDegrade && (nDegrade = GetLocalInt(oWeapon, "OnHitDegrade_" + sSpellId) > 0)) {
                        object oCreator = GetLocalObject(oWeapon, "OnHitCreator_" + sSpellId);

                        if (!GetIsObjectValid(oCreator)           ||
                            GetArea(oCreator) != GetArea(oCaster) ||
                            !GetFactionEqual(oCreator, oCaster)   ||
                            GetLocalInt(oCreator, "Rests") > GetLocalInt(oWeapon, "OnHitCreatorRests_" + sSpellId)) {

                            int nStage = GetLocalInt(oCaster, "OnHitDegradeStage_" + sSpellId) + 1;

                            if (nStage > 1)
                                DegradeOnHitProperties(oWeapon, ip, nSpellId, nDegrade, oCreator);

                            SetLocalInt(oCaster, "OnHitDegradeStage_" + sSpellId, 1);
                        } else {
                            DeleteLocalInt(oCaster, "OnHitDegradeStage_" + sSpellId);
                        }
                    }
                }
            }
        }

        ip = GetNextItemProperty(oWeapon);
    }

    struct SubString ss;

    /* apply unique on-hit scripts */
    if (sScripts != "") {
        ss.rest = sScripts;

        while (ss.rest != "") {
            ss = GetFirstSubString(ss.rest);

            ExecuteScript(ss.first, oCaster);
        }
    }

    /* if any scripts set OnHitAbort, stop further on-hit processing; don't
     * check this in the script loop proper to ensure all scripts always fire. */
    if (GetLocalInt(oWeapon, "OnHitAbort")) {
        DeleteLocalInt(oWeapon, "OnHitAbort");
        return;
    }


    /* if the weapon has any added damage types (e.g. Flame Weapon), apply them */
    if (sDamages != "") {
        int i, nDamage, nDamages = 0, nRace = GetRacialType(oTarget);
        effect eDamage;
        struct IntList il;

        ss.rest = sDamages;

        while (ss.rest != "") {
            ss = GetFirstSubString(ss.rest);
            il = GetIntList(ss.first, ",");

            /* check if a favored enemy mask was specified */
            if (il.i4) {
                if (nRace > 30 || !(il.i4 & (1 << nRace)))
                    continue;
            }

            switch (il.i1) {
                case -1:  /* AA elemental bows */
                    il.i1 = 5;
                    il.i2 = 20;
                    break;
                case -2:  /* AA exotic bows */
                    il.i1 = 4;
                    il.i2 = 20;
                    break;
            }

            if (il.i0 == DAMAGE_TYPE_NEGATIVE &&
                nRace == RACIAL_TYPE_UNDEAD   &&
                GetLocalInt(oTarget, "HealType") != DAMAGE_TYPE_POSITIVE)
                continue;

            if (nVis < 1) {
                switch (il.i0) {
                    case DAMAGE_TYPE_ACID:       nVis = VFX_COM_HIT_ACID;       break;
                    case DAMAGE_TYPE_COLD:       nVis = VFX_COM_HIT_FROST;      break;
                    case DAMAGE_TYPE_ELECTRICAL: nVis = VFX_COM_HIT_ELECTRICAL; break;
                    case DAMAGE_TYPE_FIRE:       nVis = VFX_COM_HIT_FIRE;       break;
                    case DAMAGE_TYPE_SONIC:      nVis = VFX_COM_HIT_SONIC;      break;
                    case DAMAGE_TYPE_DIVINE:     nVis = VFX_COM_HIT_DIVINE;     break;
                    case DAMAGE_TYPE_MAGICAL:    nVis = VFX_IMP_CHARM;          break;
                    case DAMAGE_TYPE_NEGATIVE:   nVis = VFX_COM_HIT_NEGATIVE;   break;
                    case DAMAGE_TYPE_POSITIVE:   nVis = VFX_IMP_MAGBLUE;        break;

                    default: nVis = VFX_COM_BLOOD_SPARK_MEDIUM; break;
                }
            }

            nDamage = il.i1 + il.i3;

            for (i = 0; i < il.i1; i++)
                nDamage += Random(il.i2);

            if (nDamages == 0)
                eDamage = EffectDamage(nDamage, il.i0);
            else
                eDamage = EffectLinkEffects(eDamage, EffectDamage(nDamage, il.i0));

            nDamages++;
        }

        /* ensure added damage types don't cause kickback */
        if (GetLocalInt(oTarget, "Feedback_1_Type")) {
            SetLocalInt(oTarget, "FeedbackIgnore", nDamages);
            SetLocalObject(oTarget, "FeedbackIgnore", oCaster);
        }

        ApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oTarget);
    }


    /* apply a visual effect if one was set, which may have been dynamically
     * determined in the damage loop */
    if (nVis > 0)
        ApplyVisualToObject(nVis, oTarget);


    /* apply special on-hit effects */
    if (sEffects != "") {
        int nPos, nDurType, nSubType, bPetrify, bSelf, bLast = FALSE;
        float fDur;
        string sDur;
        effect eLink;
        struct IntList il;
        struct SubString css;

        ss.rest = sEffects;

        /* parse each effect in turn */
        while (ss.rest != "") {
            ss  = GetFirstSubString(ss.rest);
            css = GetFirstSubString(ss.first, "|");

            /* if the effect has any pre-effect checks (e.g. random chance, saving throw),
             * make them before applying the effect */
            if (css.rest != "") {
                ss.first = css.first;

                if (css.rest == "&") {
                    if (!bLast)
                        continue;
                } else if (css.rest == "^") {
                    if (!(bLast = !bLast))
                        continue;
                } else {
                    bLast = !(CheckDynamic(css.rest, oTarget, OBJECT_SELF) > 0);

                    /* CheckDynamic returns TRUE if they pass; we invert it above so
                     * bLast is 'did the last effect go through' */
                    if (!bLast)
                        continue;
                }
            }


            if ((nPos = FindSubString(ss.first, "#")) >= 0) {
                nSpellId = StringToInt(GetStringRight(ss.first, GetStringLength(ss.first) - (nPos + 1)));
                ss.first = GetStringLeft(ss.first, nPos);

                if (GetHasSpellEffect(nSpellId, oTarget))
                    continue;
            } else
                nSpellId = -1;


            /* check if the effect has an associated visual */
            if ((nPos = FindSubString(ss.first, "/")) >= 0) {
                nVis     = StringToInt(GetStringRight(ss.first, GetStringLength(ss.first) - (nPos + 1)));
                ss.first = GetStringLeft(ss.first, nPos);
            } else
                nVis = 0;


            nSubType = 0;
            bPetrify = FALSE;
            bSelf    = FALSE;

            /* check if the effect has a duration parameter specified; if so, apply it;
             * otherwise, determine the duration of the effect automatically */
            if ((nPos = FindSubString(ss.first, ":")) >= 0) {
                sDur     = GetStringRight(ss.first, GetStringLength(ss.first) - (nPos + 1));
                ss.first = GetStringLeft(ss.first, nPos);

                /* check if a subtype was specified */
                if ((nPos = FindSubString(sDur, ":")) >= 0) {
                    string sSub = GetStringRight(sDur, 1);

                    if (sSub == "E")
                        nSubType = SUBTYPE_EXTRAORDINARY;
                    else if (sSub == "S")
                        nSubType = SUBTYPE_SUPERNATURAL;

                    sDur = GetStringLeft(sDur, nPos);
                }

                if ((nPos = FindSubString(sDur, "+")) >= 0) {
                    int nRandDur = StringToInt(GetStringRight(sDur, GetStringLength(sDur) - (nPos + 1)));

                    fDur = StringToFloat(GetStringLeft(sDur, nPos)) + Random(nRandDur);
                } else
                    fDur = StringToFloat(sDur);

                if (fDur < 0.0) {
                    fDur = 0.0;
                    nDurType = DURATION_TYPE_PERMANENT;
                } else if (fDur == 0.0)
                    nDurType = DURATION_TYPE_INSTANT;
                else
                    nDurType = DURATION_TYPE_TEMPORARY;


                int bFirst = TRUE;
                struct SubString sss;
                sss.rest = ss.first;

                while (sss.rest != "") {
                    sss = GetFirstSubString(sss.rest, ";");
                    il = GetIntList(sss.first, ",");

                    if (bFirst) {
                        bFirst = FALSE;
                        eLink  = EffectDynamic(il.i0, il.i1, il.i2, il.i3, il.i4, il.i5, il.i6);
                    } else
                        eLink = EffectLinkEffects(eLink, EffectDynamic(il.i0, il.i1, il.i2, il.i3, il.i4, il.i5, il.i6));
                }
            } else {
                il = GetIntList(ss.first, ",");

                if (il.i0 < 100) {
                    fDur     = 6.0;
                    nDurType = DURATION_TYPE_TEMPORARY;
                } else {
                    fDur     = 0.0;
                    nDurType = DURATION_TYPE_INSTANT;
                }

                if (il.i0 == EFFECT_TYPE_HEAL)
                    bSelf = TRUE;
                else if (il.i0 == EFFECT_TYPE_PETRIFY)
                    bPetrify = TRUE;

                eLink = EffectDynamic(il.i0, il.i1, il.i2, il.i3, il.i4, il.i5, il.i6);
            }

            if (bSelf) {
                if (GetCurrentHitPoints(oCaster) < GetMaxHitPoints(oCaster)) {
                    if (nVis > 0)
                        ApplyVisualToObject(nVis, oCaster);

                    ApplyEffectToObject(DURATION_TYPE_INSTANT, eLink, oCaster);
                }
            } else {
                if (nVis > 0)
                    ApplyVisualToObject(nVis, oTarget);
                else if (nVis < 0)
                    eLink = EffectLinkEffects(eLink, EffectVisualEffect(-nVis));

                if (nSubType == SUBTYPE_EXTRAORDINARY)
                    eLink = ExtraordinaryEffect(eLink);
                else if (nSubType == SUBTYPE_SUPERNATURAL)
                    eLink = SupernaturalEffect(eLink);

                if (nSpellId >= 0)
                    SetEffectSpellId(eLink, nSpellId);

      &am