Aller au contenu

Photo

Help understanding a script.


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

#26
Shadooow

Shadooow
  • Members
  • 4 465 messages
Yup, this is the real issue. I didnt wanted to mention it anywhere especially on wiki as it allows some really nasty 3xpl01ts, the one with BBoD (where you can use any epic spell to bypass immunity as well) is the reason why my patch adds a new variable-based immunity to BBoD which works in all cases.

I didnt noticed the level would changed (and assassin feats uses
Assassin level not character level) but caster level doesn't have any
effect on AOEs by default anyway as the damage is usualy fixed or it has
different effect though. Even if my patch fixes the incorrect caster
level or DC as it uses not values from functions but values stored on
AOE object which are written from original spell script).

But you are incorrect in the case of AoE and other spells when you claim its not bugged when you cast other spells. There is a big issue with ResistSpell that some players already learn to abuse. Since the ResistSpell uses last spell cast, you can very simpy penetrate eg. demilich immunity to all spells using an epic spell or non-spell-like feat like in the case in BBoD. Or you can make yourself immune. To do it, you need to cast shadow shield/globe of invulnerability/shield spell and then simply after you stack all the aoes you need you cast a necromantic/lvl 1-4/magic missile spell before you enter your AoEs. Then you will be completely immune while enemies won't. Neither my patch fixes this issue as it would mean to rewrite the spell resist function itself as its hardcoded and use a innatelevel to do it. Ugly workaround I mean, it would be easier and maybe would make more sense to remove the spell resist check for AoEs. They are not subject to resistance according to dnd rules anyway.

Modifié par ShaDoOoW, 10 novembre 2011 - 02:39 .


#27
WhiZard

WhiZard
  • Members
  • 1 204 messages

ShaDoOoW wrote...
But you are incorrect in the case of AoE and other spells when you claim its not bugged when you cast other spells. .


My claim was that the "SR roll" portion (not spell immunity or spell level absorption) was not bugged for AoEs as it used the "dispelability caster level" (of the effect) instead of the normal caster level (of the last cast spell).  Non-AoEs (like planar rift) will be bugged for all three portions as it takes its caster level from the "last cast spell" of the "caster".

Modifié par WhiZard, 09 novembre 2011 - 07:22 .


#28
Shadooow

Shadooow
  • Members
  • 4 465 messages
ok

WhiZard wrote...

Non-AoEs (like planar rift) will be bugged for all three portions as it takes its caster level from the "last cast spell" of the "caster".

Hmmm, not neccessarily. It should work fine in a lets say with ordinar special ability provided the ResistSpell function is called in the actual script and that this script only fires after spell is cast, not in onhit or from other place via ExecuteScript.

But I think we both forgot on one important thing - spell resistance comes into play only for normal spell. For a spellscripts called from a spell of UserType > 1 only spell immunity and possibly spell mantle (untested) is checked.

#29
WhiZard

WhiZard
  • Members
  • 1 204 messages

ShaDoOoW wrote...

ok

WhiZard wrote...

Non-AoEs (like planar rift) will be bugged for all three portions as it takes its caster level from the "last cast spell" of the "caster".

Hmmm, not neccessarily. It should work fine in a lets say with ordinar special ability provided the ResistSpell function is called in the actual script and that this script only fires after spell is cast, not in onhit or from other place via ExecuteScript.

"last cast spell" implies (current) in the way I defined it (just like GetLastSpellCastclass() uses current).   ResistSpell() only looks at the last information for the character ("caster") in question for usage of spells.2da (spell-casting feat using, etc.)

But I think we both forgot on one important thing - spell resistance comes into play only for normal spell. For a spellscripts called from a spell of UserType > 1 only spell immunity and possibly spell mantle (untested) is checked.

False.  User type has no significance for ResistSpell() .  Planar Rift has user type 4 and functions as expected.  ResistSpell() uses no information from the actual spell unless it happens to correspond to the last set information of the "caster", (e.g. normal spells).


EDIT:  If the "last cast spell" of the "caster" has a spell level above 9 will skip the "SR roll" automatically.  This may be the "user type" discrepancy you were seeing.

Modifié par WhiZard, 09 novembre 2011 - 09:57 .


#30
Shadooow

Shadooow
  • Members
  • 4 465 messages
False? Really? You have tested it? I did, long time ago when I encountered the BBoD bug the first time, and now again before few minutes to be extra sure.

ResistSpell return -1 if the UserType is not 1. See epic spells are 2, its not about InnateLevel which is for other special abilities lesser than 10 and the ResistSpell still returns -1. The BBoD is an exception as its triggered from onhit where it does uses the spell informations of the last spell cast so if the last spell cast was of UserType 1 it, the ResistSpell will work as that spell so i will try to remove spell mantle with last spell cast spell level, then it might be stopped by a spell immunity of caster is immune to the school or spell of the last spell cast and then it will try to penetrate SR (with a caster level of last spell cast). Since the last spell cast after casting BBoD is the BBoD spell it will work that way until the caster uses anything with a UserType of 2+, then the ResistSpell simply return -1 like it would normally for such ability.

Modifié par ShaDoOoW, 09 novembre 2011 - 11:15 .


#31
WhiZard

WhiZard
  • Members
  • 1 204 messages

ShaDoOoW wrote...

False? Really? You have tested it? I did, long time ago when I encountered the BBoD bug the first time, and now again before few minutes to be extra sure.

ResistSpell return -1 if the UserType is not 1.

I have gone through all four user types and user type 2 and 4 have the -1 return value.  This value only pertains to its associated script when the "caster" is the caster of the spell.  If you enter an AoE after using a user type 2 or 4 the -1 value will not be reflected.  This looks like another peculiarity like AoEs using the "dispel caster level" for their caster level.

its not about InnateLevel which is for other special abilities lesser than 10 and the ResistSpell still returns -1.

the innate level does have significance in other circumstances.


EDIT: User type 4, now has significance but only for the caster being the "caster" same constraint applies to user type 2.

Modifié par WhiZard, 10 novembre 2011 - 03:16 .


#32
Shadooow

Shadooow
  • Members
  • 4 465 messages
fine, thats something what I havent tested this time :D good job

#33
henesua

henesua
  • Members
  • 3 858 messages

Lightfoot8 wrote...
So in order to do it right you would have to figure out what book the spell was being cast from. unfortunatly we just have no way of doing that in nwscript. At least not a way that I know of.  


I believe this is the function that you are looking for:

// Returns the class that the spellcaster cast the
// spell as.
// - Returns class_TYPE_INVALID if the caster has
// no valid class (placeables, etc...)
int GetLastSpellCastclass()

This function will return a class value when the character casts a spell. If the spell is activated by a feat, it will return class_TYPE_INVALID.

-edit-
just noticed that the forum does not like the word class in all caps, and puts it in all lower case. weird.

Modifié par henesua, 10 novembre 2011 - 03:06 .


#34
Shadooow

Shadooow
  • Members
  • 4 465 messages
henesua: Problem is that this function, same as all other spell related function doesn't work properly outside of spellscript which is this case. The script cast fake Acid Splash spell. But the actual damage and DC is computed before this action is done. Thats the reason to do such workaround in the first place.

BUT, is a question why it is done this way, the OP didnt provided enough inromations as whats the purpose etc. but I guess it could be done via simply ActionCastSpell with bCheat = TRUE. It has one downside and that is the metamagic doesnt work and the caster level in such case is (InnateLevel*2)-1 min 10 (or something like that, WhiZard will correct me if Im wrong). But in this case of Acid Splash or low lvl environment in general, it shouldn't matter (unless the acid splash was only an example). If it would matter, then thats where my patch comes handy - it allows to override caster level, DC and metamagic used (provided the spell in question uses script with new spell engine, if there is modified script in module/hak it wont work ).

Also I noticed that even fake spell (once is cast) setup some spell informations. So i the worst case the actual spell script could be fires after the fakespell action is done using actiondocommand. However I dont know for sure which spell informations are set this way, but definitely not all of them.

#35
henesua

henesua
  • Members
  • 3 858 messages
Hmmm... I'm not fully conversant in how these things work. I've been using that function in a custom spellhook. I guess that counts as a spell script because it is called by the spell script?

Anyway, thanks. Good information as usual.

#36
WhiZard

WhiZard
  • Members
  • 1 204 messages

ShaDoOoW wrote...
 and the caster level in such case is (InnateLevel*2)-1 min 10 (or something like that, WhiZard will correct me if Im wrong).

You are right on.

However I dont know for sure which spell informations are set this way, but definitely not all of them.

The big one not included is the Spell ID link for the script.  Thus the effects it creates are not labeled as Spell effects (GetHasSpellEffect() and GetEffectSpellID() won't work properly)  and these effects can often stack from recasting.

#37
Shadooow

Shadooow
  • Members
  • 4 465 messages

WhiZard wrote...

ShaDoOoW wrote...

False? Really? You have tested it? I did, long time ago when I encountered the BBoD bug the first time, and now again before few minutes to be extra sure.

ResistSpell return -1 if the UserType is not 1.

I have gone through all four user types and user type 2 and 4 have the -1 return value.  This value only pertains to its associated script when the "caster" is the caster of the spell.  If you enter an AoE after using a user type 2 or 4 the -1 value will not be reflected.  This looks like another peculiarity like AoEs using the "dispel caster level" for their caster level.

its not about InnateLevel which is for other special abilities lesser than 10 and the ResistSpell still returns -1.

the innate level does have significance in other circumstances.


EDIT: User type 4, now has significance but only for the caster being the "caster" same constraint applies to user type 2.


Did you run any more tests? I found out that the spell immunity is still checked even if the UserType is 2. But only spell immunity part of ResistSpell check. Didnt tried higher values yet.

#38
WhiZard

WhiZard
  • Members
  • 1 204 messages

ShaDoOoW wrote...
Did you run any more tests? I found out that the spell immunity is still checked even if the UserType is 2. But only spell immunity part of ResistSpell check. Didnt tried higher values yet.


Can't quite tell the context.

On  -1 spells then, no I haven't pursued beyond verifying the -1s exist only on user type 2 and 4 and do not carry over to AoEs.

For AoEs in general, the user type doesn't matter. Only the last cast spell feat flag (not user type 3, but whether or not it was used as a feat), the last cast spell properties (Spell ID and school) and the "dispelability caster level" seem to have significance.

#39
Shadooow

Shadooow
  • Members
  • 4 465 messages
Yea, right about AOEs, I tried the same solution for "immunity feedback workaroud" for BBoD as I did for petrification and ResistSpell returned 0, although I granted the immunity to the last spell cast.

Modifié par ShaDoOoW, 19 novembre 2011 - 07:34 .


#40
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Our custom AOE scripts have workarounds for those issues - you set the needed info on the aoe at time of casting, and check it instead of calling on the buggy functions. Code is acaos'. You'll have to disregard references to Herald of Storms - it's one of our quasis. Likewise, the #pragma stuff is for Skywing's compiler, which won't allow includes to contain void mains otherwise. I'll post the include first, then the acid fog spell:

ac_aoeper_inc
#include "hg_inc"
#include "ac_spell_inc"

int GetIsHeraldAoE(object oAoE) {
    string sTag = GetTag(oAoE);
    if (sTag == "VFX_PER_FOGFREEZE"     ||
        sTag == "VFX_AOE_CONSECRATE_20" ||
        sTag == "VFX_PER_FOGACID"       ||
        sTag == "VFX_PER_FOGKILL"       ||
        sTag == "VFX_PER_FOGMIND"       ||
        sTag == "VFX_PER_FOGSTINK")
        return TRUE;

    return FALSE;
}

struct SpellInfo GetPersistentAoESpellInfo ();
#pragma default_function(GetPersistentAoESpellInfo)
int GetPersistentAoELimit (struct SpellInfo si);
#pragma default_function(GetPersistentAoELimit)
int GetPersistentAoEEffect (struct SpellInfo si);
#pragma default_function(GetPersistentAoEEffect)
string GetPersistentAoETag (struct SpellInfo si);
#pragma default_function(GetPersistentAoETag)
string GetPersistentAoEName (struct SpellInfo si);
#pragma default_function(GetPersistentAoEName)
int GetPersistentAoETargetMask (struct SpellInfo si);
#pragma default_function(GetPersistentAoETargetMask)
int GetPersistentAoETargetType (struct SpellInfo si);
#pragma default_function(GetPersistentAoETargetType)

float GetPersistentAoEDuration (struct SpellInfo si);
#pragma default_function(GetPersistentAoEDuration)

effect GetPersistentAoEImpactEffect (struct SpellInfo si);
#pragma default_function(GetPersistentAoEImpactEffect)
effect GetPersistentAoEDurationEffect (struct SpellInfo si);
#pragma default_function(GetPersistentAoEDurationEffect)
effect GetPersistentAoEVisualEffect (struct SpellInfo si);
#pragma default_function(GetPersistentAoEVisualEffect)

void ApplyAoEEffect (struct SpellInfo si, object oAoE, object oTarget, effect eEff, effect eDur, effect eVis);
#pragma default_function(ApplyAoEEffect)

float GetPersistentAoERemaining (object oAoE) {
    int nRemaining = GetLocalInt(oAoE, "AoEExpires") - GetLocalInt(GetModule(), "uptime");

    return IntToFloat(nRemaining < 1 ? 1 : nRemaining);
}


void DoAoEHeartbeat (struct SpellInfo si, object oAoE, effect eEff, effect eDur, effect eVis) {
    int bDestroy = FALSE, bAllPCs = FALSE;

    if (!GetIsObjectValid(oAoE))
        return;


    /* Storm of Vengeance doesn't destroy properly sometimes */
    if (GetPersistentAoEEffect(si) == AOE_PER_STORM) {
        if (si.id == SPELL_STORM_OF_VENGEANCE && GetLocalInt(GetArea(oAoE), "Area_Underwater"))
            bAllPCs = TRUE;

        if (GetLocalInt(GetModule(), "uptime") > GetLocalInt(oAoE, "AoEExpires"))
            bDestroy = TRUE;
    }

    if (bDestroy                     ||
        !GetIsObjectValid(si.caster) ||
        GetIsDead(si.caster)         ||
        GetArea(si.caster) != si.area) {

        SetPlotFlag(oAoE, FALSE);
        DestroyObject(oAoE);
        return;
    }

    DelayCommand(6.0, DoAoEHeartbeat(si, oAoE, eEff, eDur, eVis));
    AddLocalInt(oAoE, "AoERounds", 1);

    if (!LineOfSightObject(oAoE, si.caster))//PAoEs have no effect when caster out of LoS
        return;


    si.target = oAoE;
    int nMask = GetPersistentAoETargetMask(si);
    int nType = GetPersistentAoETargetType(si);

    for (si.target = GetFirstInPersistentObject(oAoE, nMask);
         GetIsObjectValid(si.target);
         si.target = GetNextInPersistentObject(oAoE, nMask)) {

        if (GetIsSpellTarget(si, si.target, nType) || (bAllPCs && GetIsPC(si.target))) {
            SignalEvent(si.target, EventSpellCastAt(si.caster, si.id));

            AssignCommand(si.caster, DelayCommand(GetRandomDelay(0.5, 4.5), ApplyAoEEffect(si, oAoE, si.target, eEff, eDur, eVis)));
        }
    }
}

void StartAoEHeartbeat (struct SpellInfo si, string sTag, effect eEff, effect eDur, effect eVis, int nUntil) {
    int nCount = 1;
    object oLoc, oAoE;

    do {
        oLoc = GetNearestObjectToLocation(OBJECT_TYPE_ALL, si.loc, nCount++);
    } while (GetTag(oLoc) == sTag);

    nCount = 1;
    oAoE = GetNearestObjectByTag(sTag, oLoc);

    while (GetIsObjectValid(oAoE)) {
        if (GetAreaOfEffectCreator(oAoE) == si.caster && !GetLocalInt(oAoE, "AoEHeartbeat")) {

            SetLocalInt(oAoE, "AoEHeartbeat", 1);
            SetLocalInt(oAoE, "AoEExpires", nUntil);
            SetLocalInt(oAoE, "AoESP", si.sp);

            DelayCommand(0.0, DoAoEHeartbeat(si, oAoE, eEff, eDur, eVis));
            return;
        }

        oAoE = GetNearestObjectByTag(sTag, oLoc, ++nCount);
    }
}

void main () {
    struct SpellInfo si = GetPersistentAoESpellInfo();
    if (si.id < 0)
        return;

    float fDur  = GetPersistentAoEDuration(si);
    string sTag = GetPersistentAoETag(si);
    effect eAoE = EffectAreaOfEffect(GetPersistentAoEEffect(si),
        (si.id == SPELL_GREASE ? "nw_s0_greasea" : "****"), "****", "****");


    int nLimit = GetPersistentAoELimit(si);

    if (nLimit > 0) {
        int nFound = 0, nCount = 1;
        object oOther, oCreator, oArea = GetArea(si.caster);

        while (GetIsObjectValid(oOther = GetNearestObjectByTag(sTag, si.caster, nCount++))) {
            oCreator = GetAreaOfEffectCreator(oOther);
            if (oCreator == oArea                                       ||
                GetLocalInt(oOther, "NoAoEDispel"))
                continue;

            if (GetIsPC(si.caster) && GetIsPC(oCreator)) {
                if (GetIsHeraldAoE(oOther)) {
                    if (!GetIsQuasiclass(QUASIclass_HERALD_OF_STORMS, oCreator))
                        nFound++;
                } else
                    nFound++;
            } else if (GetFactionEqual(si.caster, oCreator)) {
                nFound++;
            }

            if (nFound >= nLimit) {
                SetPlotFlag(oOther, FALSE);
                DestroyObject(oOther);

                if (nFound == nLimit)
                    FloatingTextStringOnCreature("To prevent lag, only " +
                        IntToString(nLimit) + " " + GetPersistentAoEName(si) +
                        " spell" + (nLimit == 1 ? "" : "s") + " may exist in an area at the same time.", si.caster);
            }
        }
    } else if (nLimit == -1) {
        object oAoE, oCreator, oArea = GetArea(si.caster);

        for (oAoE = GetFirstObjectInArea(oArea); GetIsObjectValid(oAoE); oAoE = GetNextObjectInArea(oArea)) {

            oCreator = GetAreaOfEffectCreator(oAoE);
            if (GetObjectType(oAoE) != OBJECT_TYPE_AREA_OF_EFFECT ||
                oCreator != si.caster)
                continue;

            if (GetIsQuasiclass(QUASIclass_HERALD_OF_STORMS, oCreator) && //heralds ignore herald spells in this check - that limit handled in -2
                GetIsHeraldAoE(oAoE))
                continue;

            if (FindSubString(" VFX_PER_FOGACID VFX_PER_FOGFIRE VFX_PER_FOGFREEZE VFX_PER_STORM VFX_PER_RAINFIRE VFX_PER_RAINFREEZE VFX_AOE_CONSECRATE_20 ",
                              " " + GetTag(oAoE) + " ") >= 0) {
                SetPlotFlag(oAoE, FALSE);
                DestroyObject(oAoE);

                FloatingTextStringOnCreature("Only one area of effect damage field may exist per caster in an area at the same time.", si.caster);
            }
        }
    } else if (nLimit == -2) {
        object oAoE, oArea = GetArea(si.caster);
        int nFound;
        for (oAoE = GetFirstObjectInArea(oArea); GetIsObjectValid(oAoE); oAoE = GetNextObjectInArea(oArea)) {
            if (GetObjectType(oAoE) != OBJECT_TYPE_AREA_OF_EFFECT ||
                !GetIsQuasiclass(QUASIclass_HERALD_OF_STORMS, GetAreaOfEffectCreator(oAoE)))
                continue;

            if (FindSubString(" VFX_PER_FOGACID VFX_PER_FOGSTINK VFX_PER_FOGKILL VFX_AOE_CONSECRATE_20 VFX_PER_FOGFREEZE VFX_PER_FOGMIND ",
                              " " + GetTag(oAoE) + " ") >= 0) {
                nFound++;
                if (nFound >= 2) {
                    SetPlotFlag(oAoE, FALSE);
                    DestroyObject(oAoE);

                    if (nFound == 2)
                        FloatingTextStringOnCreature("Only two herald of storms clouds may exist in an area at the same time.", si.caster);
                }
            }
        }
    }


    effect eEff = GetPersistentAoEImpactEffect(si);
    effect eDur = GetPersistentAoEDurationEffect(si);
    effect eVis = GetPersistentAoEVisualEffect(si);


    AssignCommand(si.area, DelayCommand(2.0, StartAoEHeartbeat(si, sTag, eEff, eDur, eVis,
        GetLocalInt(GetModule(), "uptime") + FloatToInt(fDur))));

    ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, eAoE, si.loc, fDur);
}




nw_s0_acidfog
#include "ac_aoeper_inc"


struct SpellInfo GetPersistentAoESpellInfo ()        { return GetSpellInfo(); }

int GetPersistentAoELimit (struct SpellInfo si)      {
    if (GetIsQuasiSpell(si, QUASIclass_HERALD_OF_STORMS))
        return -2;
    return -1;
}
int GetPersistentAoEEffect (struct SpellInfo si)     { return AOE_PER_FOGACID; }
string GetPersistentAoETag (struct SpellInfo si)     { return "VFX_PER_FOGACID"; }
string GetPersistentAoEName (struct SpellInfo si)    { return "Acid Fog"; }
int GetPersistentAoETargetMask (struct SpellInfo si) { return OBJECT_TYPE_CREATURE; }
int GetPersistentAoETargetType (struct SpellInfo si) { return TARGET_TYPE_DEFAULT; }


float GetPersistentAoEDuration (struct SpellInfo si) {
    return MetaDuration(si, si.clevel / 2, DURATION_IN_ROUNDS);
}

effect GetPersistentAoEImpactEffect (struct SpellInfo si) {
    effect eEff;
    return eEff;
}

effect GetPersistentAoEDurationEffect (struct SpellInfo si) {
    effect eDur;

    if (GetIsQuasiSpell(si, QUASIclass_HERALD_OF_STORMS))
        eDur = EffectDamageIncrease(1);
    return eDur;
}

effect GetPersistentAoEVisualEffect (struct SpellInfo si) {
    ApplyVisualAtLocation(VFX_FNF_GAS_EXPLOSION_ACID, si.loc);

    return EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_ACID);
}


void ApplyAoEEffect (struct SpellInfo si, object oAoE, object oTarget, effect eEff, effect eDur, effect eVis) {
    if (!GetSpellResisted(si, oTarget, 0.0, 2)) {
        int nDamage, nDice = si.clevel, nEffType = GetEffectType(eDur);
        int nDamType = DAMAGE_TYPE_ACID, nSaveType = SAVING_THROW_TYPE_ACID;

        if (nDice < 1)
            nDice = 1;

        nDamage = MetaPower(si, nDice, 6);
        nDamage = GetEvasionAdjustedDamage(nDamage, oTarget, si.dc, nSaveType|SAVING_THROW_FLAG_SPELL, si.caster);

        if (nDamage > 0) {
            if (nEffType == EFFECT_TYPE_DAMAGE_IMMUNITY_DECREASE) {
                if (!GetHasSpecificEffect(eDur, oTarget))
                    ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDur, oTarget);

                eEff = EffectDamage(nDamage / 2, nDamType);
                DelayCommand(0.1, ApplyEffectToObject(DURATION_TYPE_INSTANT, eEff, oTarget));

                eEff = EffectDamage(nDamage / 2, DAMAGE_TYPE_MAGICAL);
                DelayCommand(0.1, ApplyEffectToObject(DURATION_TYPE_INSTANT, eEff, oTarget));
            } else if (nEffType == EFFECT_TYPE_DAMAGE_INCREASE) {
                if (GetTrueDamageImmunity(oTarget, nDamType) < 100) {
                    int nDecrease = 25 - GetTotalDamageImmunityDecrease(oTarget, nDamType);

                    if (nDecrease > 5)
                        nDecrease = 5;

                    if (nDecrease > 0) {
                        eEff = EffectDamageImmunityDecrease(nDamType, nDecrease);
                        ApplyEffectToObject(DURATION_TYPE_PERMANENT, eEff, oTarget);
                    }
                }

                eEff = EffectDamage(nDamage, nDamType);
                ApplyEffectToObject(DURATION_TYPE_INSTANT, eEff, oTarget);
            } else {
                eEff = EffectDamage(nDamage, nDamType);
                ApplyEffectToObject(DURATION_TYPE_INSTANT, eEff, oTarget);
            }

            ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oTarget);
        }
    }
}


Funky

#41
Shadooow

Shadooow
  • Members
  • 4 465 messages
I can't see how you solved the main issue we are talking about, ResistSpell? I do use similar workaround like you and I have correct values stored as local int on AOE, but since ResistSpell is hardcoded there is no way to pass them inside this function. I tried to make a fake ResistSpell function that would behave like the default one, but I dropped that approach due to the technical limitations.

#42
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Sorry, it's in GetSpellResisted, in our spells include - should've pasted it as well. We bypass the issue by passing the the original sp value in the si struct. Here's the function:

int GetSpellResisted (struct SpellInfo si, object oTarget=OBJECT_INVALID, float fDelay=0.0, int bSROnly=FALSE) {
    int nSR = 0, nResisted = 0, nRoll = 0;

    if (!GetIsObjectValid(oTarget))
        oTarget = si.target;
    if (!GetIsObjectValid(oTarget))
        return 0;

    if (GetObjectType(oTarget) == OBJECT_TYPE_CREATURE)
        nSR = GetSpellResistance(oTarget);
    else
        nSR = GetLocalInt(oTarget, "SR");


    if (nSR > 0 && GetHasSpellImmunity(SPELL_SPELL_RESISTANCE, oTarget)) {
        nSR -= GetLocalInt(oTarget, "TauntResult");
        if (nSR < 1)
            nSR = 1;
    }


    if (nSR > 0 && si.sp >= 0 && !GetFactionEqual(si.caster, oTarget)) {
        nRoll = d20(1);

        if (si.sp + nRoll < nSR)
            nResisted = 1;
    }

    if (nResisted == 0) {
        if (bSROnly) {
            if (bSROnly == 2 && GetHasSpellImmunity(si.id, oTarget))
                nResisted = 2;
        } else {
            switch (ResistSpell(si.caster, oTarget)) {
                case 2:  /* globe or spell immunity */
                    nResisted = 2;
                    break;
                case 3:  /* spell mantle */
                    if (si.sp >= 0)
                        nResisted = 3;
                    break;
            }
        }
    }

    if (!nResisted) {
        if (nRoll > 0)
            SendSpellResistanceMessage(si.caster, oTarget, nRoll, si.sp, nSR, fDelay);

        return 0;
    }

    if (GetObjectType(oTarget) == OBJECT_TYPE_CREATURE) {
        effect eVis;

        switch (nResisted) {
            case 2:
                nSR  = -2;
                eVis = EffectVisualEffect(VFX_IMP_GLOBE_USE);
                break;
            case 3:
                nSR  = -3;
                eVis = EffectVisualEffect(VFX_IMP_SPELL_MANTLE_USE);
                break;
            default:
                eVis = EffectVisualEffect(VFX_IMP_MAGIC_RESISTANCE_USE);
                break;
        }

        DelayCommand(fDelay, ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oTarget));
    }

    if (nRoll > 0 || nSR < -1)
        SendSpellResistanceMessage(si.caster, oTarget, nRoll, si.sp, nSR, fDelay);

    return nResisted;
}

Funky

#43
Shadooow

Shadooow
  • Members
  • 4 465 messages

[code=auto:0]if (nResisted == 0) {
        if (bSROnly) {
            if (bSROnly == 2 && GetHasSpellImmunity(si.id, oTarget))
                nResisted = 2;
        } else {
            switch (ResistSpell(si.caster, oTarget)) {
                case 2:  /* globe or spell immunity */
                    nResisted = 2;
                    break;
                case 3:  /* spell mantle */
                    if (si.sp >= 0)
                        nResisted = 3;
                    break;
            }
        }
    }

See this is it. I ended up with the same issue so I abadoned that aproach for now. If the target fail in fake spell resistance roll and neither is immune to the correct AOE spell, then you call the ResistSpell function which is hardcoded and which uses the wrong spell id of something that target could be immune of.

Also the ResistSpell does SR check again, so in case that the fake SR check failed due to the fact that you have changed the caster level downstairs, second SR check will be probably successful.

True, your solution handles the main issue which is fact that AOE spell can bypass target immunities. But caster still can make the AOE spell to get immunity himself.

Modifié par ShaDoOoW, 23 novembre 2011 - 01:07 .


#44
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

ShaDoOoW wrote...

See this is it. I ended up with the same issue so I abadoned that aproach for now. If the target fail in fake spell resistance roll and neither is immune to the correct AOE spell, then you call the ResistSpell function which is hardcoded and which uses the wrong spell id of something that target could be immune of.

Wrong. Read it again. ResistSpell is only called to drain mantles.

Also the ResistSpell does SR check again, so in case that the fake SR check failed due to the fact that you have changed the caster level downstairs, second SR check will be probably successful.

You need to check the returns on the ResistSpell function and then read the script again.

Funky

#45
Shadooow

Shadooow
  • Members
  • 4 465 messages
I assumed this function is called from AOE spell like incendiary cloud with oTarget == si.Caster and bSROnly = FALSE

Lets say that last spell cast was finger of death.

si.id = incendiary cloud
si.sp = caster level of lets say 20

real id = FoD
real caster level = 20



int GetSpellResisted (struct SpellInfo si, object oTarget=OBJECT_INVALID, float fDelay=0.0, int bSROnly=FALSE) {
    int nSR = 0, nResisted = 0, nRoll = 0;

    if (!GetIsObjectValid(oTarget))
        oTarget = si.target;
    if (!GetIsObjectValid(oTarget))
        return 0;

    if (GetObjectType(oTarget) == OBJECT_TYPE_CREATURE)
        nSR = GetSpellResistance(oTarget); <<nSR = lets say 30
    else
        nSR = GetLocalInt(oTarget, "SR");


    if (nSR > 0 && GetHasSpellImmunity(SPELL_SPELL_RESISTANCE, oTarget)) {
        nSR -= GetLocalInt(oTarget, "TauntResult");
        if (nSR < 1)
            nSR = 1;
    }


    if (nSR > 0 && si.sp >= 0 && !GetFactionEqual(si.caster, oTarget)) { <<FALSE
        nRoll = d20(1);

        if (si.sp + nRoll < nSR)
            nResisted = 1;
    }

    if (nResisted == 0) { << TRUE
        if (bSROnly) { << FALSE
            if (bSROnly == 2 && GetHasSpellImmunity(si.id, oTarget))
                nResisted = 2;
        } else { << TRUE
            switch (ResistSpell(si.caster, oTarget)) { << ResistSpell with real id and caster level
                case 2:  /* globe or spell immunity */ << TRUE caster doesnt have mantle but have shadow shield
                    nResisted = 2;
                    break;
                case 3:  /* spell mantle */
                    if (si.sp >= 0)
                        nResisted = 3;
                    break;
            }
        }
    }

    if (!nResisted) {
        if (nRoll > 0)
            SendSpellResistanceMessage(si.caster, oTarget, nRoll, si.sp, nSR, fDelay);

        return 0;
    }

    if (GetObjectType(oTarget) == OBJECT_TYPE_CREATURE) {
        effect eVis;

        switch (nResisted) {
            case 2:
                nSR  = -2;
                eVis = EffectVisualEffect(VFX_IMP_GLOBE_USE);
                break;
            case 3:
                nSR  = -3;
                eVis = EffectVisualEffect(VFX_IMP_SPELL_MANTLE_USE);
                break;
            default:
                eVis = EffectVisualEffect(VFX_IMP_MAGIC_RESISTANCE_USE);
                break;
        }

        DelayCommand(fDelay, ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oTarget));
    }

    if (nRoll > 0 || nSR < -1)
        SendSpellResistanceMessage(si.caster, oTarget, nRoll, si.sp, nSR, fDelay);

    return nResisted;
}

Modifié par ShaDoOoW, 23 novembre 2011 - 09:19 .


#46
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Was there a question in there?

Funky

#47
Shadooow

Shadooow
  • Members
  • 4 465 messages
Yes. Where am I wrong there (i guess my assumption that si.sp is caster level is wrong)? How does it work then and how does it solves my issue?

#48
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Si.sp casterlevel is correct, because it is passed in the struct from when the spell is initially cast. Take a look at the AOE script again. Casterlevel is never rechecked after the initial cast; rather it is passed via struct - you could just as easily pass it via variable, but we use that struct for all our spells, to make edits to the spell system at larger simpler.

Funky

#49
Shadooow

Shadooow
  • Members
  • 4 465 messages
I see, your AOEs passing a bSROnly = 2 thus you are checking only spell resistance and spell immunity. I havent though of that as I wanted the spell mantle functionality as well, BUT if I think about it, fact that spell mantle doesnt work for AOE might be better balanced then default behavior. Even if not its a small price for fixing the ResistSpell 3xpl0it. So, thanks, I will think about this approach though it means I will have to print fake feedback (which might be issue when client has different language installed than server)

#50
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Here's our Get and Send messages to get you started. I imagine you'll be able to find some tlk values for them that fit. The PC Filter option you can ignore - we use it, combined with NWNX, to allow players to only see some messages in combat, instead of the superscroll of normal combat.

struct SubString GetSpellResistanceMessage (object oCaster, object oTarget, int nRoll, int nSP, int nSR) {
    struct SubString ss;
    int bCasterBrief = GetPCFilter(oCaster, PCFILTER_BRIEF);
    int bTargetBrief = GetPCFilter(oTarget, PCFILTER_BRIEF);

    string sCaster = (bCasterBrief ? "SP" : "Spell Penetration");
    string sTarget = (bTargetBrief ? "SR" : "Spell Resistance");


    if (nSR == -2) {
        ss.first = C_TEAL + GetName(oTarget) + " : " + sCaster + " : *immune*" + C_END;
        ss.rest  = C_TEAL + GetName(oCaster) + " : " + sTarget + " : *immune*" + C_END;
    } else if (nSR == -3) {
        ss.first = C_TEAL + GetName(oTarget) + " : " + sCaster + ": *absorbed*" + C_END;
        ss.rest  = C_TEAL + GetName(oCaster) + " : " + sTarget + ": *absorbed*" + C_END;
    } else {
        string sCasterRoll = "(" + IntToString(nRoll) + " + " + IntToString(nSP) +
            " = " + IntToString(nRoll + nSP) + (bCasterBrief ? " / " : " vs. SR: " ) +
            IntToString(nSR) + ")";
        string sTargetRoll = "(" + IntToString(nRoll) + " + " + IntToString(nSP) +
            " = " + IntToString(nRoll + nSP) + (bTargetBrief ? " / " : " vs. SR: " ) +
            IntToString(nSR) + ")";

        if (nSP + nRoll >= nSR) {
            ss.first = C_TEAL + GetName(oTarget) + " : " + sCaster + " : *success* : "  + sCasterRoll + C_END;
            ss.rest  = C_TEAL + GetName(oCaster) + " : " + sTarget + " : *defeated* : " + sTargetRoll + C_END;
        } else {
            ss.first = C_TEAL + GetName(oTarget) + " : " + sCaster + " : *failure* : "  + sCasterRoll + C_END;
            ss.rest  = C_TEAL + GetName(oCaster) + " : " + sTarget + " : *resisted* : " + sTargetRoll + C_END;
        }
    }

    return ss;
}

void SendSpellResistanceMessage (object oCaster, object oTarget, int nRoll, int nSP, int nSR, float fDelay=0.0) {
    struct SubString ss = GetSpellResistanceMessage(oCaster, oTarget, nRoll, nSP, nSR);

    switch (GetPCFilter(oCaster, PCFILTER_SPELLPEN)) {
        case 0: DelayCommand(fDelay, SendMessageToPC(oCaster, ss.first));                     break;
        case 1: DelayCommand(fDelay, SendSystemMessage(oCaster, ss.first));                   break;
        case 2: DelayCommand(fDelay, FloatingTextStringOnCreature(ss.first, oCaster, FALSE)); break;
    }

    switch (GetPCFilter(oTarget, PCFILTER_SPELLPEN)) {
        case 0: DelayCommand(fDelay, SendMessageToPC(oTarget, ss.rest));                      break;
        case 1: DelayCommand(fDelay, SendSystemMessage(oTarget, ss.rest));                    break;
        case 2: DelayCommand(fDelay, FloatingTextStringOnCreature(ss.rest, oTarget, FALSE));  break;
    }
}

Funky