Nwn2Fixes – direct download
[ part1 ]
Hi KevL,
I thought it best to continue responses here …
Further testing with dominate effect … The main advantage is that dominated creatures are still attacked by the enemy as opposed to charmed ones. Also …
A) Henchmen (as opposed to companions) of party (if used) do not appear to show dominate or charmed effects at all. So these may have to be “Stunned” instead (that works).
B) I ended up promoting the Stunned effect above even the Charmed effect, as (in my own tests), stunned targets appeared to remain targets, whereas charmed ones are left until uncharmed. Therefore, a PC is more at risk being stunned than charmed (which reflects dominate better in my opinion).
Now I just have to update all scripts that reference these scaling functions. I will also add these functions to scripts that use EffectFrightened, so that does actually make the change.
QUESTION TO ALL: Are there any spells that you believe need to be considered having these functions added that may not already be making reference to them?
@kevL_s If you have the time and are able to look over the scripts (below) and see if you can see if there is any potential issue, then please let me know. Otherwise, I’ll assume they are relatively well balanced, work and ready to go. Thanks! EDIT: I found a potential bug in my core scripts while testing this … so maybe there will be a potential v4 of v1.21 update.
Thanks, Lance.
UPDATE: As a Dominated PC is still immediately attacked by an enemy (which immediately ends the domination effect), I am considering changing it to something else … possibly stunned.
This is what I believe the final GetEffectScaled (with supporting function) and the new GetScaledDuration should become …
////////////////////////////////////////////////////////////////////////////
// SCALED FIXES (LB UPDATE) - AFFECTS SPELL SCALING EFFECT WHEN OPTIONS CHANGED
// NO LONGER MAKING ANY DISTINCTION FOR "VERY EASY" SERVER SETTING
////////////////////////////////////////////////////////////////////////////
effect FIXEDGetScaledEffect(effect eStandard, object oTarget);
effect FIXEDGetScaledEffect(effect eStandard, object oTarget)
{
// CHECK IF A PARTY MEMBER
object oPC = GetFactionLeader(oTarget);
// CHECK IF A PARTY MEMBER
object oMaster = GetMaster(oTarget);
object oMasterCheck = GetFactionLeader(oMaster);
effect eNew = eStandard;
// MINIMUM ALLOWED IS NOW 1 (EASY)
int nDiff = GetGameDifficulty();
if(nDiff == 0){nDiff = 1;}
if(oPC != OBJECT_INVALID || oMasterCheck != OBJECT_INVALID)
{
//SendMessageToAllPCs("PARTY MEMBER TARGETED");
//////////////////////////////////////////////////////////////////////////////////////////
// DIFFICULTY SETTING OVERRIDES OTHER SETTINGS ANYWAY
//////////////////////////////////////////////////////////////////////////////////////////
if(nDiff < GAME_DIFFICULTY_CORE_RULES)
{
/////////////////////////////////////////////////////////////////////////////////////
// NORMAL SETTING CHANGES NB: DURATIONS ALSO AFFECTED
/////////////////////////////////////////////////////////////////////////////////////
if(nDiff == GAME_DIFFICULTY_NORMAL)
{
// DOMINATE LOWERS TO CHARM FOR PC/COMPANIONS
if(GetEffectType(eStandard) == EFFECT_TYPE_DOMINATED || GetEffectType(eStandard) == EFFECT_TYPE_CHARMED)
{
// MAIN PC OR COMPANION
if(GetAssociateType(oTarget) == ASSOCIATE_TYPE_NONE)
{
eNew = EffectCharmed();
}
// AN ASSOCIATE OF SOME TYPE (DOMINATE LOWERED TO STUN)
else if(GetEffectType(eStandard) == EFFECT_TYPE_DOMINATED)
{
eNew = EffectStunned();
}
// AN ASSOCIATE OF SOME TYPE (CHARM LOWERED TO STUN)
else
{
eNew = EffectDazed();
}
}
// FEAR/FRIGHTENED BECOME DAZED INSTEAD ON NORMAL
else if(GetEffectType(eStandard) == EFFECT_TYPE_FRIGHTENED){eNew = EffectDazed();}
// PARALYZE/STUN/FEAR (NORMAL SETTING)
else if(GetEffectType(eStandard) == EFFECT_TYPE_PARALYZE || GetEffectType(eStandard) == EFFECT_TYPE_STUNNED
|| GetEffectType(eStandard) == EFFECT_TYPE_CONFUSED)
{
// NORMAL HAS NO CHANGE (AS PER ORIGINAL OPTIONS) ALTHOUGH DURATION ARE SHORTENED
}
}
/////////////////////////////////////////////////////////////////////////////////////
// EASY SETTING CHANGES NB: DURATIONS ALSO AFFECTED
/////////////////////////////////////////////////////////////////////////////////////
else if(nDiff == GAME_DIFFICULTY_EASY)
{
// DOMINATE/CHARM LOWERS TO DAZED ON EASY
if(GetEffectType(eStandard) == EFFECT_TYPE_DOMINATED || GetEffectType(eStandard) == EFFECT_TYPE_CHARMED)
{
eNew = EffectDazed();
}
// FEAR/FRIGHTENED BECOME -4 ATTACK PENALTIES INSTEAD ON EASY
else if(GetEffectType(eStandard) == EFFECT_TYPE_FRIGHTENED){eNew = EffectAttackDecrease(4);}
// PARALYZE BECOME JUST STUNNED INSTEAD
else if(GetEffectType(eStandard) == EFFECT_TYPE_PARALYZE)
{
eNew = EffectStunned();
}
// CONFUSED/STUN BECOME JUST DAZED INSTEAD
else if(GetEffectType(eStandard) == EFFECT_TYPE_CONFUSED || GetEffectType(eStandard) == EFFECT_TYPE_STUNNED)
{
eNew = EffectDazed();
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// SPECIAL HARD CORE RULE TREATMENT: FOR MAIN PC / NORMAL COMPANIONS (NO ACTION REQUIRED)
//////////////////////////////////////////////////////////////////////////////////////////
else if(GetAssociateType(oTarget) == ASSOCIATE_TYPE_NONE)
{
// CHANGE DOMINATE TO STUN TO BE MORE EFFECTIVE (KEEP ATTACKING WHILE STUNNED)
if(GetEffectType(eStandard) == EFFECT_TYPE_DOMINATED)
{
eNew = EffectStunned();
}
//SendMessageToAllPCs("PC OR COMP TARGETED ... " + GetName(oTarget));
}
//////////////////////////////////////////////////////////////////////////////////////////
// SPECIAL HARD CORE RULE TREATMENT: ASSOCIATES MUST BE HANDLED ACCORDING TO TYPE
//////////////////////////////////////////////////////////////////////////////////////////
else if(!GetIsDead(oTarget))
{
//SendMessageToAllPCs("OTHER TARGETED ... " + GetName(oTarget));
// ENSURE ANY DOMINATED TARGETED ASSOCIATE IS SIMPLY STUNNED OR UNSUMMONED
if(GetEffectType(eStandard) == EFFECT_TYPE_DOMINATED || GetEffectType(eStandard) == EFFECT_TYPE_CHARMED)
{
// HENCHMEN DO NOT ACCEPT DOMINATE EOR CHARM EFFECTS
if(GetAssociateType(oTarget) == ASSOCIATE_TYPE_HENCHMAN)
{
eNew = EffectStunned();
}
else
{
eNew = EffectDeath();
}
}
}
}
return eNew;
}
////////////////////////////////////////////////////////////////////////////
// SCALED DUARATIONS (LB UPDATE) - AFFECTS SPELL SCALING EFFECT WHEN OPTIONS CHANGED
// NO LONGER MAKING ANY DISTINCTION FOR "VERY EASY" SERVER SETTING
////////////////////////////////////////////////////////////////////////////
int FIXEDGetScaledDuration(int nActualDuration, object oTarget);
int FIXEDGetScaledDuration(int nActualDuration, object oTarget)
{
// CHECK IF A PARTY MEMBER
object oPC = GetFactionLeader(oTarget);
// CHECK IF A PARTY MEMBER
object oMaster = GetMaster(oTarget);
object oMasterCheck = GetFactionLeader(oMaster);
int nNew = nActualDuration;
// MINIMUM ALLOWED IS NOW 1 (EASY)
int nDiff = GetGameDifficulty();
if(nDiff == 0){nDiff = 1;}
if(oPC != OBJECT_INVALID || oMasterCheck != OBJECT_INVALID)
{
//SendMessageToAllPCs("PARTY MEMBER TARGETED");
if(nActualDuration > 3)
{
// EASY OR VERY EASY
if(nDiff < GAME_DIFFICULTY_NORMAL)
{
//nNew = nActualDuration / 4;
float fNew = 0.5 * IntToFloat(nActualDuration);
nNew = FloatToInt(fNew);
}
// NORMAL
else if(nDiff == GAME_DIFFICULTY_NORMAL)
{
//nNew = nActualDuration / 2;
float fNew = 0.75 * IntToFloat(nActualDuration);
nNew = FloatToInt(fNew);
}
if(nNew == 0)
{
nNew = 1;
}
}
}
return nNew;
}
typo
// MAIN PC OR COMPANION
if(GetAssociateType(oTarget) != ASSOCIATE_TYPE_NONE)
{
eNew = EffectCharmed();
}
main pc or companion == associate_type_none
also you shouldn’t have to check for masters and leaders of masters; if you want any/all party-faction this ought cover it
if (GetIsObjectValid(GetFactionLeader(oTarget)))
{
// hi - I'm in a PC party ;)
}
Hi KevL,
Good catch … I had been messing around with the script for a bit, so became blind to that! I’ll update that now.
Yes, I thought that would be the case … and even the check for not dead at the end is probably not required too. I just felt a little nervous about every other associate, inc Familiars, Animal Companions, Summons, Henchmen … I mean I originally had that, but there was one check where it did not return a summoned iirc … but then it may have been because I had not recompiled the edited function. (My concentration was a bit lacking today. )
I will double check some of my own tests and requirements tomorrow (especially henchmen) and update after checking.
Thanks for looking over … It was definitely required!
Lance.
a different perspective ( w/ basically the same input and output )
// 'scaledeffect_inc'
////////////////////////////////////////////////////////////////////////////////
// SCALED EFFECTS (LB UPDATE)
// - AFFECTS TYPE OF MENTAL EFFECTS ALLOWED ACCORDING TO GAME DIFFICULTY SETTING
////////////////////////////////////////////////////////////////////////////////
effect FIXEDGetScaledEffect(effect eType, object oTarget)
{
if (GetIsObjectValid(GetFactionLeader(oTarget)))
{
int iType = GetEffectType(eType);
switch (GetGameDifficulty())
{
case GAME_DIFFICULTY_VERY_EASY:
case GAME_DIFFICULTY_EASY:
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
case EFFECT_TYPE_CONFUSED:
case EFFECT_TYPE_STUNNED:
case EFFECT_TYPE_CHARMED:
return EffectDazed();
case EFFECT_TYPE_PARALYZE:
return EffectStunned();
case EFFECT_TYPE_FRIGHTENED:
return EffectAttackDecrease(4);
}
break;
case GAME_DIFFICULTY_NORMAL:
switch (GetAssociateType(oTarget))
{
case ASSOCIATE_TYPE_NONE:
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
return EffectCharmed();
case EFFECT_TYPE_FRIGHTENED:
return EffectDazed();
}
break;
default:
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
return EffectStunned();
case EFFECT_TYPE_CHARMED:
case EFFECT_TYPE_FRIGHTENED:
return EffectDazed();
}
break;
}
break;
default:
case GAME_DIFFICULTY_CORE_RULES:
case GAME_DIFFICULTY_DIFFICULT:
switch (GetAssociateType(oTarget))
{
default:
case ASSOCIATE_TYPE_NONE:
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
return EffectStunned();
}
break;
case ASSOCIATE_TYPE_HENCHMAN:
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
case EFFECT_TYPE_CHARMED:
return EffectStunned();
}
break;
case ASSOCIATE_TYPE_ANIMALCOMPANION:
case ASSOCIATE_TYPE_FAMILIAR:
case ASSOCIATE_TYPE_SUMMONED:
case ASSOCIATE_TYPE_DOMINATED:
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
case EFFECT_TYPE_CHARMED:
return EffectDeath(FALSE, FALSE, TRUE); // NOTE: death is a bit drastic for a Charm spell
// TODO: CHARMED -> Stun (per henchmen)
}
break;
}
break;
}
}
return eType;
}
////////////////////////////////////////////////////////////////////////////////
// SCALED DURATIONS (LB UPDATE)
// - AFFECTS DURATION OF MENTAL EFFECTS ACCORDING TO GAME DIFFICULTY SETTING
////////////////////////////////////////////////////////////////////////////////
int FIXEDGetScaledDuration(int iDur, object oTarget)
{
if (GetIsObjectValid(GetFactionLeader(oTarget)))
{
switch (GetGameDifficulty())
{
case GAME_DIFFICULTY_VERY_EASY:
case GAME_DIFFICULTY_EASY:
iDur = (iDur + 1) / 2; // 0.50 dur round up
break;
case GAME_DIFFICULTY_NORMAL:
iDur = (iDur * 3 + 3) / 4; // 0.75 dur round up
break;
}
}
return iDur;
}
//void main(){}
Hi KevL,
EDIT: I just thought I saw a typo (ASSOCIATE_TYPE_DOMINATED) and then realised that there is such a state! I had no idea! Cool! Amazing how something can be right in front of your face all these years and not see it! I mean it sits there right next to all those others I know so well …
That’s really neat!
I am going to embrace that piece of code and use it as a working example for myself from now on.
I really must try to use switch more often myself… It’s another one of those coding aspects that takes me too long to think about compared to the way I normally do things. However, I can see the benefits of doing it the way you have, and actually … this example has demonstrated to me some of those questions I had about switch. i.e. You can have two or more cases heading the return value … and that you can embed switch statements inside other switch statements. Again, I kind of knew that may be possible, but always opted for the other method of coding, which I “knew” would work.
And the math you used for the duration (especially the second one) is rather interesting … I mean that as I look at the numbers, I can kind of see where it comes from, but I would not have known if integers would work well like that … nor would I have had the confidence to do it. Question: Do integers always round up from 0.5? i.e. Does 0.5 (and above) go to 1, but anything below that go to 0?
I also note you say, “NOTE: death is a bit drastic for a Charm spell”, followed by the “ToDo”, and I think you are correct … Although, I believe I should have actually made them “Charmed” (i.e. Unchanged effect), as that works normally if I recall correctly, and without any other detrimental effect. (In other words, just remove the “case EFFECT_TYPE_CHARMED:” so that the original charm effect passes, and I will have to update the descriptive text to say that all party members are affected as normal APART from henchmen who are stunned instead.) In fact, the only reason I think I had henchmen stunned was because they did not show the icon and I don’t think it worked as expected on them.
I am now going to implement your scripts and test my module again … especially with respect to henchmen, just to make sure I had it as I thought.
Excellent feedback!
Thanks, Lance.
BROKEN CODE REMOVED
Integers are always rounded down. If you want integers to round up from 0.5 you have to add 1/2 to them. If you always want them to round up add (x-1)/x like @kevL_s did with iDur there.
@cuiilv
But what if iDur was passed as zero? Wouldn’t the result of 0.5 be rounded down? Or is there something specific about the way this works I did not understand?
EDIT: Actually, let me make that clearer … I mean, I know it should pass zero, but what if we wanted to return a minimum of 1? (In case zero was passed and we still needed the minimum?)
I guess that’s a different question … so the point may be irrelevant.
I guess I was going off at a tangent in my thinking … sorry about that!
Thanks, Lance.
iDur = (iDur + 1) / 2;
Half of 0 rounded up is still 0. The 0.5 is just added behind the scene to make things work. If you want a minimum of 1 just change it to
iDur = (iDur + 1) / 2;
if(iDur < 1)
iDur = 1;
Yes, I realised this point as I was replying. I was just thinking “out of context” with respect to wanting a minimum result of 1 based upon the formula used without the if(iDur < 1) section.
i.e. I automatically was implementing “what happens if” problem of a zero being passed, assuming I had missed something about the way integers worked.
My actual original question should have been … What if somehow a zero was passed? i.e. We need a minimum of 1 to return, so I wrongly thought I had misunderstood how integers rounded, rather than make the assumption of at least a one being passed.
My crazy thinking again …
Thanks, Lance.
This all came about because of this bit of code in the original OC GetScaledDuaration, which I imagine was in place because their formulae could equate to zero, unlike the way KevL does it:-
if(nNew == 0)
{
nNew = 1;
}
P.S. I’m having a concentration lacking week …
as i learned switch/case more and more and have become very comfortable with them … i ain’t ever going back :)
A good habit to get into, though, is to format your switch before actually filling it in:
switch ()
case :
break;
that’s a template. Add more case/breaks as needed
Why? Because it’s too risky to miss that break; statement.
And if you want a fallthrough case after some conditional code, write // no break; as a reminder that there shouldn’t be a break (so that when you look at it a month later you don’t sit there scratching yer head for a couple hours)
switch (i)
case 0:
if (j == 1)
break;
// no break;
case 1:
break;
it’s also a good idea to specify a default case. (and don’t bother writing cases that do nothing – comment them out if you want them simply as a reminder of what’s possible but missing)
I don’t understand the question … what do integers have to do with floats?
What my code does is leverage/exploit the fact that an int divided by an int drops any remainder.
3/4=0
But if [1 less than the denominator] is added to the numerator, the result effectively rounds up. (note, “rounds” is probly the wrong word: the result gets promoted higher…)
(3+3)/4=1
What this does for the scaled-duration function, is that 0 as input should be returned as 0 (and handled at the other end, so to speak), but anything greater than 0 gets a non-zero value returned.
- without messing around with floats, which have their own quirks and tricks ofc.
ok. Sounds good,
uh, best not to judge an effect merely by its icon … icons can fail to be applied (if an effect is applied without DelayCommand(0.1, ApplyEffect()) … sometimes). i don’t know why, just another nwn2 bug …
( if you want to see a creature’s current effects, best use a debug script and print em to chat )
really, zero should not be passed in; if your temporary effect could have zero duration then it should be checked and stopped before doing a bunch of stuff that’s going to ~poof~ anyway.
Funny you should say that … I ended up just adding a ton of brackets to your script trying to understand where the break affected it … It looks a lot messier than your script, but at least I see where it comes into play now. In fact, I may try reversing some of those brackets (take them away again) to help reduce the crowded look, but leave the main ones that include the break so I can still see what is going on … You have a clearer understanding in your head than I do, so I still need some of those brackets … If you are OK with it, I will repost your script, with the added brackets (shorter version) for you to double check I still have it right?
Good point!
See my response above … I had gone way off … on a tangent!
Still checking …
OK, I will try looking at this again, but I think this consistently failed, even when they had no other effects and I know the effect was definitely “failed against” according to debug anyway.
Yes, I was sidetracked in my thinking … hopefully this brain fog will pass soon. It comes and goes … and sometimes I am not even aware I am in it … unless my wife says something. The main problem is seeing the trail of poor thinking I have been through afterwards … and why I use the EDIT button so much!
This was the minimum amount of bracket usage my head can currently “write” with … I was able to “remove” all those I placed around the return stuff, but could not do without the rest for my head to comprehend how it was written … even though I understood what it was doing.
Is this still correct? … my assumption is that it is, but at the moment, I would prefer to double-check.
CODE REMOVED - SEE LATER POST
sure …
note there are times when you sort of need brackets …
to declare variables inside of cases
but to get around that, vars can be declared before the switch starts
Hi KevL,
OK, I’ll try to note that … btw, post edited above just for you to double check.
Thanks, Lance.
it doesn’t strike me as quite correct … they way you seem to think of
default:
is incorrect. Go back and look at my usage, and think of default as just another case that’s on equal terms with any other case(s) that are on the same level of indentation
don’t scope defaults like they are some kind of super-case. They’re not – they’re just another case and should stay at the level where they were …
Hi KevL,
That was going to be my exact question to you next … about the default usage. I think its inclusion is what throws my thinking a bit … I mean why do we even have it, if we know a certain case is going to be passed?
It feels like my trying to account for a zero pass in the other function we talked about. i.e. Is it even required?
I’ll try to rewrite and edit post for you to check again if that’s OK … Back in a bit.
Thanks, Lance.
EDIT: OK, here is my confusion, as it seems to be this especially in the NORMAL switch part … it seems like the “default” is behaving like its own “case” … following all the same rules as if it is a switch of some type … albeit a default “catch-all one”. therefore, shouldn’t it be treated with brackets in the same way as a normal case distinction?
case ASSOCIATE_TYPE_NONE:
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
return EffectCharmed();
case EFFECT_TYPE_FRIGHTENED:
return EffectDazed();
}
break;
default:
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
return EffectStunned();
case EFFECT_TYPE_CHARMED:
case EFFECT_TYPE_FRIGHTENED:
return EffectDazed();
}
break;
in that case it is its own case ;)
→
i could have left them (defaults) out for GetGameDifficulty and GetAssociateType (under hardcore/difficult)
but the default case for GetAssociateType (under normal) is serving a purpose by handling all associate types other than NONE. (ie, this default could be replaced by cases of all other associate types)
i put them in because … nwn2
I mean if i knew with 100% absolute certainty that there can be no other GameDifficulty values and AssociateType values, i wouldna bother w/ defaults …
(except under normal because it’s just so convenient there – in fact sometimes if it’s just an if/else like that i’d simply use if/else)
→
this is okay
case ASSOCIATE_TYPE_NONE:
{
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
return EffectCharmed();
case EFFECT_TYPE_FRIGHTENED:
return EffectDazed();
}
break;
}
default:
{
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
return EffectStunned();
case EFFECT_TYPE_CHARMED:
case EFFECT_TYPE_FRIGHTENED:
return EffectDazed();
}
break;
}
Just when I thought I was getting it.
I thought that would be OK. Phew!
So … where I have commented the “defaults” sections below, would that still be OK/work?
And so is the first “default” in the previous post good to keep because it is acting as a “default” drop through “case”? EDIT: I think you just confirmed that as OK above in your own edit.
EDIT: Also wouldn’t this bit of code … where the default occurs before the other checks make it fire before making the other checks, or do switch case not work “in order” if you see what I mean?
default:
case ASSOCIATE_TYPE_NONE:
Thanks, Lance.
//default:
//{
case GAME_DIFFICULTY_CORE_RULES:
case GAME_DIFFICULTY_DIFFICULT:
{
switch (GetAssociateType(oTarget))
{
//default:
//{
case ASSOCIATE_TYPE_NONE:
{
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
return EffectStunned();
}
break;
}
case ASSOCIATE_TYPE_HENCHMAN:
{
switch (iType)
{
case EFFECT_TYPE_DOMINATED:
case EFFECT_TYPE_CHARMED:
return EffectStunned();
}
break;
}
case ASSOCIATE_TYPE_ANIMALCOMPANION:
case ASSOCIATE_TYPE_FAMILIAR:
case ASSOCIATE_TYPE_SUMMONED:
case ASSOCIATE_TYPE_DOMINATED:
{
switch (iType)
{
//case EFFECT_TYPE_CHARMED:
case EFFECT_TYPE_DOMINATED:
return EffectDeath(FALSE, FALSE, TRUE); // NOTE: death is a bit drastic for a Charm spell
// TODO: CHARMED -> Stun (per henchmen) NOW RETURN AS DEFAULT CHARMED
}
break;
}
//}
}
break;
}
//}
right. The way you had bracketed out those defaults, scoping them outside the cases that they actually belonged with, irked me.
either deleting them or leaving them in but on the same level as the cases they belong with is ok …
gonna watch some vids and doze off asap :)
that’s one of the beauties of switch/case – they don’t have to work in sequence like if/else code.
Note that they will go in sequence until a break. But when the switch is first entered, codeflow “jumps” directly to a particular case. So there could be a thousand cases and boom that’s where codeflow goes/begins.