Just Curious - Dual Wielding


#1

Hi All,

This has probably been answered long ago, but I have not looked closely before …

According to the rules, Adaur can wield two weapons (a club and a dagger) , which have a -4 and -8 penalty. He has a +2 strength, which means his final penalties are - 2 and -6, which are “correctly” given as feedback during the combat - all well and good.

However, my question is, why do they figures say that his penalties are -3 and -7 in the character sheet when the weapons are equipped?

Thanks in advance, Lance.


#2

Perhaps his Str was drained to below 14?


#3

The PC had only just started their adventuring career. :slight_smile:

i.e. I started a new game with the stock Adaur PC and then dual-armed him with a club and a dagger. Then the character sheet said: -7 on off-hand and -3 on main hand.

I thought nothing of it until I had a combat, which then said the penalties were -6 and -2, which reflected the PC’s bonus of +2 according to the correct default penalty figures of -8 and -4.

I am struggling to see why the character sheet does not either say -6 and -2 (reflecting the strength bonuses), or -8 and -4 (not reflecting the strength bonuses).

I did wonder if the racial penalty for daylight vision was affecting it, but changing the day/night did not appear to alter this figure as far as I could see.

Any other thoughts … I suppose I could test a human.
Cheers, Lance.


#4

The only other thing that came to mind is the Weapon Finesse feat, but of course Adaur doesn’t have that and the club isn’t a light weapon. Perhaps a bug then?


#5

Apparently the light blindness feat is bugged (during daytime). I just did a test on it. Here’s the results:

Exterior area during day time, with feat = Light penalty applied: -1.
Exterior area during night time, with feat = Light penalty applied: -1.
Exterior area during day time, feat removed = Light penalty removed.
Exterior area during night time, feat removed = Light penalty removed.
Interior area = Light penalty removed.

I also did another test setting the “interior” flag to TRUE on an exterior area and sure enough, the penalty was removed even during the day time!
Also tested on an interior area with the “interior” flag to FALSE - penalty remained.

So, it looks like light blindness is only effected by the interior/exterior flag, not Day/night.


#6

Ah Thank you Travus!

That saved me having to try to track it down. I just finished sorting my own issues and posted. I’m glad you managed to save me the time of trying to track it myself. Funny enough, I just had another issue with the GetIsDay function not working as I though it would.

SO, must remember its to do with interior/exterior only.

So that puts that mystery to bed - Thanks again!
Lance.


#7

I suppose one could at least partly address the bug with a lightweight module script, run on spawn in/out and at dusk/dawn.


#8

it’s not only that this feat’s appearance is bugged, it’s also not used in combat resolution though it shows up in the character sheet. a potential fix should take manually number changing into consideration too.


#9

I did some additional testing. I have to agree with Lance that the -1 penalty is not actually being included in the die roll with regard to combat, even though the character sheet has the penalty applied. I also used the ‘enablecombatdebugging’ command and I saw no change to the die roll in external/internal areas as well as as day/night. I tested with melee and missile weapons.
However, it IS being applied to skill checks (but not saves). I tested saves and skill checks with traps and taunting. The character sheet also applies the penalty to the saves /skills. Note, the save/skill checks are also only effected by the interior/exterior flag, not day/night.
On a side note, the Daylight Adaptation feat does cancel out the penalty from Light Blindness.


#10

Good work Travus!

So, one solution may be to write some code that “corrects” the character sheet for the Light Blindness penalty being included when it is not in practice … and then update the Light Blindness feat description to say that it affects saves/skill checks, but not combat.

Alternatively (and if a builder has a means to easily amend individual PC stats within their module/campaign that are a permanent … or adapted the FEAT spellID section) , then a form of the -1 penalty may be able to be applied to combat again. Although, I do not know if this -1 would then also update the character sheet section compounding the problem - if you see what I mean.

Anyone else think which would be the “cleanest” way to deal with the code? Or not worth it?

Cheers, Lance.


#11

Here’s a promising solution. Seems to work very well. Put it in the module’s OnHeartbeat script. It essentially replaces the bugged daylight blindness/sensitivity feats with Local int/strings on the PC. It works in day/night and interior/exterior. Although, I’m not sure how it will work with other effects on top of these. Should also be able to modify it for the gb_comp_heart and nw_c2_default1 scripts for NPCs. I’d love someone to test this out.

void main()
{
	object oPC = GetFirstPC(FALSE);
	
	if ((GetHasFeat(1767, oPC) || GetHasFeat(1768, oPC)) && !GetHasFeat(FEAT_DAYLIGHT_ADAPATION, oPC))
	{
		if (GetHasFeat(1767, oPC))
		{
			SetLocalString(oPC, "featName", "Daylight Blindness! ");
			FeatRemove(oPC, 1767);
			SetLocalInt(oPC, "featPenalty", 1);
		}
		
		else if (GetHasFeat(1768, oPC))
		{
			SetLocalString(oPC, "featName", "Daylight Sensitivity. ");
			FeatRemove(oPC, 1768);
			SetLocalInt(oPC, "featPenalty", 2);
		} 
	}		
	
	object oArea = GetArea(oPC);	
	effect eATKpen = EffectAttackDecrease(GetLocalInt(oPC, "featPenalty"));
	effect eSavePen = EffectSavingThrowDecrease(SAVING_THROW_ALL, GetLocalInt(oPC, "featPenalty"));
	effect eSkillPen = EffectSkillDecrease(SKILL_ALL_SKILLS, GetLocalInt(oPC, "featPenalty"));
	effect eLink = EffectLinkEffects(eATKpen, eSavePen);
	eLink = EffectLinkEffects(eLink, eSkillPen);
	
	if (!GetIsAreaInterior(oArea) && GetIsDay())
	{
	 	if (!GetLocalInt(oPC, "light") && GetLocalInt(oPC, "featPenalty") > 0)
		{
			SetLocalInt(oPC, "light", 1);
			SetLocalInt(oPC, "dark", 0);
			DelayCommand(0.5f, AssignCommand(oPC, PlaySound("sfx_drain", TRUE)));
			DelayCommand(0.5f, SetNoticeText(oPC, "You are experiencing " + GetLocalString(oPC, "featName")));
			SendMessageToPC(oPC, "You are experiencing " + GetLocalString(oPC, "featName") + "Attack, saves and skills are reduced by " + IntToString(GetLocalInt(oPC, "featPenalty")));
			ApplyEffectToObject(DURATION_TYPE_PERMANENT, SupernaturalEffect(eLink), oPC);
		}	
	}
	
	if (GetIsAreaInterior(oArea) || GetIsNight())
	{
		if (!GetLocalInt(oPC, "dark") && GetLocalInt(oPC, "featPenalty") > 0)
		{
			SetLocalInt(oPC, "dark", 1);
			SetLocalInt(oPC, "light", 0);	
			DelayCommand(0.5f, AssignCommand(oPC, PlaySound("sfx_portalexplodingsmall", TRUE)));
			DelayCommand(0.5f, SetNoticeText(oPC, "You are no longer experiencing " + GetLocalString(oPC, "featName")));
			SendMessageToPC(oPC, "You are no longer experiencing " + GetLocalString(oPC, "featName"));

			effect eEffect = GetFirstEffect(oPC);
			while (GetIsEffectValid(eEffect))
			{
				if (GetEffectType(eEffect) == EFFECT_TYPE_ATTACK_DECREASE) RemoveEffect(oPC, eEffect);
				else if (GetEffectType(eEffect) == EFFECT_TYPE_SAVING_THROW_DECREASE) RemoveEffect(oPC, eEffect);
				else if (GetEffectType(eEffect) == EFFECT_TYPE_SKILL_DECREASE) RemoveEffect(oPC, eEffect);
				eEffect = GetNextEffect(oPC);
			}
		}
	}
}

#12

Hi Travus,

Goodness me, that was quick!

Unfortunately, I am too occupied with fixing some of my own code to do much else at the moment. However, as a quick piece of feedback for you … Personally, I prefer to not use the MODULE heartbeat if I can help it - I actually just had to fix another issue of my own that worked with the heartbeat script - but I see why you do here. However, there may be an issue here when catering for all PCs in a party and especially if we include MP support too. That was my second point. This may fix “a” first pc in a game (although I have not checked it), but I don’t think this will capture every PC due to the way it grabs the PC object.

That all said, and to contradict myself, I think using a heartbeat may be the best solution, but use the PC/companions heartbeat. That would solve both problems at once. I think I found a way of using a heartbeat on the main pc in the same way as a companion.

Anyway … It’s certainly a start and something I may have more time to address later if nobody else grabs the code and addresses it before me. I know Kev_L is a good candidate for fixing code, so maybe he may run by and take a look if he’s not too busy. :slight_smile:

Thanks, Lance.


#13

@travus
@Lance_Botelle

// 'fix_lightblind'
/*
    Fixes the bugs wrt/ LightBlindness (GrayOrc/Drow) or LightSensitivity
    (GrayDwarf) by removing the feat altogether and applying scripted effects.

    If the character acquires DaylightAdaptation the proper feat is restored
    since DaylightAdaptation is not bugged.

    NOTE: Is intended as a heartbeat script and currently is coded only for the
    FirstPC.
*/
// travus 2019 feb 11
// kevL 2019 feb 12 - modified.

/**
 * Constants
 */
const int SPELLID_LIGHTPENALTY = 52873; // arbitrary - shall be unique.

const int FEAT_LIGHT_BLINDNESS   = 1767; // GrayOrc or Drow
const int FEAT_LIGHT_SENSITIVITY = 1768; // GrayDwarf

const string VAR_PENALTY = "LightPenalty"; // (int) character's current penalty-val/status

const int PENALTY_NOTAPPLICABLE = -1; // * character has neither LightBlindness nor LightSensitivity
const int PENALTY_INIT          =  0; // assess or re-assess status
const int PENALTY_BLINDNESS     =  1; // * has LightBlindness
const int PENALTY_SENSITIVITY   =  2; // * has LightSensitivity

// * LightBlindness/LightSensitivity is assumed to be a racial feat that a
//   character will have from its creation. (ie, if either feat is granted to a
//   character during play this will not work; because VAR_PENALTY has already
//   been set to PENALTY_NOTAPPLICABLE)

const string VAR_STATE = "LightPenaltyState"; // (int) tracks the change from daytonight/nighttoday

const int STATE_NIGHT = 0; // penalty off
const int STATE_DAY   = 1; // penalty on


/**
 * Declarations
 */
void ClearPenalty(object oTarget, int iPenalty);
void ApplyPenalty(object oTarget, int iPenalty);

int Initialize(object oTarget);

void RestoreFeat(object oTarget, int iPenalty);


/**
 * MAIN
 */
void main()
{
    object oTarget = GetFirstPC();

    int iPenalty = GetLocalInt(oTarget, VAR_PENALTY);
    if (iPenalty != PENALTY_NOTAPPLICABLE)
    {
        if (!GetHasFeat(FEAT_DAYLIGHT_ADAPATION, oTarget))
        {
            if (iPenalty == PENALTY_INIT)
                iPenalty = Initialize(oTarget);

            int iState = GetLocalInt(oTarget, VAR_STATE);

            if (GetIsNight() || GetIsAreaInterior(GetArea(oTarget)))
            {
                if (iState == STATE_DAY)
                {
                    SetLocalInt(oTarget, VAR_STATE, STATE_NIGHT);
                    ClearPenalty(oTarget, iPenalty);
                }
            }
            else if (iState == STATE_NIGHT)
            {
                SetLocalInt(oTarget, VAR_STATE, STATE_DAY);
                ApplyPenalty(oTarget, iPenalty);
            }
        }
        else if (iPenalty != PENALTY_INIT)
        {
            RestoreFeat(oTarget, iPenalty);
        }
    }
}


/**
 * Definitions
 */

// Clears any penalties from Blindness or Sensitivity.
void ClearPenalty(object oTarget, int iPenalty)
{
    string sFeat;
    switch (iPenalty)
    {
        case PENALTY_BLINDNESS:   sFeat = "Light Blindness";   break;
        case PENALTY_SENSITIVITY: sFeat = "Light Sensitivity"; break;

        default: return;
    }

    DelayCommand(0.5f, AssignCommand(oTarget, PlaySound("sfx_portalexplodingsmall", TRUE)));
    DelayCommand(0.5f, SetNoticeText(oTarget, "You are no longer experiencing " + sFeat));

    SendMessageToPC(oTarget, "<c=limegreen>You are no longer experiencing " + sFeat + ".</c>");

    effect eEffect = GetFirstEffect(oTarget);
    while (GetIsEffectValid(eEffect))
    {
        if (GetEffectSpellId(eEffect) == SPELLID_LIGHTPENALTY)
        {
            RemoveEffect(oTarget, eEffect);
            eEffect = GetFirstEffect(oTarget);
        }
        else
            eEffect = GetNextEffect(oTarget);
    }
}

// Applies penalties for Blindness or Sensitivity.
void ApplyPenalty(object oTarget, int iPenalty)
{
    string sFeat;
    switch (iPenalty)
    {
        case PENALTY_BLINDNESS:   sFeat = "Light Blindness !";   break;
        case PENALTY_SENSITIVITY: sFeat = "Light Sensitivity !"; break;

        default: return;
    }

    effect eAtkDecr   = EffectAttackDecrease(iPenalty);
    effect eSaveDecr  = EffectSavingThrowDecrease(SAVING_THROW_ALL, iPenalty);
    effect eSkillDecr = EffectSkillDecrease(SKILL_ALL_SKILLS, iPenalty);

    eAtkDecr = EffectLinkEffects(eAtkDecr, eSaveDecr);
    eAtkDecr = EffectLinkEffects(eAtkDecr, eSkillDecr);
    eAtkDecr = SupernaturalEffect(eAtkDecr);
    eAtkDecr = SetEffectSpellId(eAtkDecr, SPELLID_LIGHTPENALTY);

    ApplyEffectToObject(DURATION_TYPE_PERMANENT, eAtkDecr, oTarget);

    DelayCommand(0.5f, AssignCommand(oTarget, PlaySound("sfx_drain", TRUE)));
    DelayCommand(0.5f, SetNoticeText(oTarget, "You are experiencing " + sFeat));

    SendMessageToPC(oTarget, "<c=firebrick>You are experiencing " + sFeat
        + "\nAttacks, Saves and Skills are reduced by " + IntToString(iPenalty) + "</c>");
}

// Initializes oTarget's penalty-type and removes the feat itself.
// - returns -1 if not applicable
int Initialize(object oTarget)
{
    int iPenalty;

    if (GetHasFeat(FEAT_LIGHT_SENSITIVITY, oTarget))
    {
        FeatRemove(oTarget, FEAT_LIGHT_SENSITIVITY);
        iPenalty = PENALTY_SENSITIVITY;
    }
    else if (GetHasFeat(FEAT_LIGHT_BLINDNESS, oTarget))
    {
        FeatRemove(oTarget, FEAT_LIGHT_BLINDNESS);
        iPenalty = PENALTY_BLINDNESS;
    }
    else
        iPenalty = PENALTY_NOTAPPLICABLE;

    SetLocalInt(oTarget, VAR_PENALTY, iPenalty);
    return iPenalty;
}

// Restores the proper feat if oTarget gets DaylightAdaptation.
// - note that delevel could again wipe off DaylightAdaptation
void RestoreFeat(object oTarget, int iPenalty)
{
    int iFeatId;
    switch (iPenalty)
    {
        case PENALTY_BLINDNESS:   iFeatId = FEAT_LIGHT_BLINDNESS;   break;
        case PENALTY_SENSITIVITY: iFeatId = FEAT_LIGHT_SENSITIVITY; break;

        default: return;
    }

    FeatAdd(oTarget, iFeatId, FALSE);

    if (GetLocalInt(oTarget, VAR_STATE) == STATE_DAY)
        ClearPenalty(oTarget, iPenalty);

    DeleteLocalInt(oTarget, VAR_STATE);
    DeleteLocalInt(oTarget, VAR_PENALTY);
}

#14

It’s a thing of beauty!


#15

Hi KevL,

Thanks again for stepping in … That looks like I will be able to place it on my PCs heartbeat scripts and have it apply via OBJECT_SELF. (I have adjusted the scriptsets.2da so that the PC and companions can have the same heartbeat script and differentiated within the script.) I will give it a go.

That would then work for every PC a player controls.

Cheers, Lance.

EDIT: Yes. I just tried the following adaptation by placing the code in my own feat include files and calling the function FixLightBlind from the PC heartbeat and it worked fine. :slight_smile:

By the way, I worked out why my GetIsDay appeared awry. It is because the “day” did not start on the hour, but the hour after the module setting. I did not know that “dusk” had its own hour. I realised this after allowing time to pass another hour (15 minutes real time in my campaign) with this code when it had not worked at the expected hour. i.e. Dusk started at 1800 hours, but the “night” started at 1900. That explains my other issue I had when timing with GetIsDay.

EDIT 2: I used -1004 for my unique number just because I have used -1000 backwards for my own campaign ones.

Thanks for putting the time into this KevL!

/* Constants */
const int SPELLID_LIGHTPENALTY = -1004; 	// arbitrary - shall be unique 52873.
const int FEAT_LIGHT_BLINDNESS   = 1767; 	// GrayOrc or Drow
const int FEAT_LIGHT_SENSITIVITY = 1768; 	// GrayDwarf
const string VAR_PENALTY = "LightPenalty"; 	// (int) character's current penalty-val/status
const int PENALTY_NOTAPPLICABLE = -1; 		// * character has neither LightBlindness nor LightSensitivity
const int PENALTY_INIT          =  0; 		// assess or re-assess status
const int PENALTY_BLINDNESS     =  1; 		// * has LightBlindness
const int PENALTY_SENSITIVITY   =  2; 		// * has LightSensitivity
const string VAR_STATE = "LightPenaltyState"; // (int) tracks the change from daytonight/nighttoday
const int STATE_NIGHT = 0; 					// penalty off
const int STATE_DAY   = 1; 					// penalty on

////////////////////////////////////////////////////////////////////////
// Clears any penalties from Blindness or Sensitivity.
////////////////////////////////////////////////////////////////////////
void ClearPenalty(object oTarget, int iPenalty);
void ClearPenalty(object oTarget, int iPenalty)
{
    string sFeat;
    switch (iPenalty)
    {
        case PENALTY_BLINDNESS:   sFeat = "Light Blindness";   break;
        case PENALTY_SENSITIVITY: sFeat = "Light Sensitivity"; break;

        default: return;
    }

    DelayCommand(0.5f, AssignCommand(oTarget, PlaySound("sfx_portalexplodingsmall", TRUE)));
    DelayCommand(0.5f, SetNoticeText(oTarget, "You are no longer experiencing " + sFeat));

    SendMessageToPC(oTarget, "<c=limegreen>You are no longer experiencing " + sFeat + ".</c>");

    effect eEffect = GetFirstEffect(oTarget);
	
    while (GetIsEffectValid(eEffect))
    {
        if (GetEffectSpellId(eEffect) == SPELLID_LIGHTPENALTY)
        {
            RemoveEffect(oTarget, eEffect);
            eEffect = GetFirstEffect(oTarget);
        }
        
		else
		{
            eEffect = GetNextEffect(oTarget);
		}
    }
}
	
////////////////////////////////////////////////////////////////////////
// Applies penalties for Blindness or Sensitivity.
////////////////////////////////////////////////////////////////////////
void ApplyPenalty(object oTarget, int iPenalty);
void ApplyPenalty(object oTarget, int iPenalty)
{
    string sFeat;
    switch (iPenalty)
    {
        case PENALTY_BLINDNESS:   sFeat = "Light Blindness !";   break;
        case PENALTY_SENSITIVITY: sFeat = "Light Sensitivity !"; break;

        default: return;
    }

    effect eAtkDecr   = EffectAttackDecrease(iPenalty);
    effect eSaveDecr  = EffectSavingThrowDecrease(SAVING_THROW_ALL, iPenalty);
    effect eSkillDecr = EffectSkillDecrease(SKILL_ALL_SKILLS, iPenalty);

    eAtkDecr = EffectLinkEffects(eAtkDecr, eSaveDecr);
    eAtkDecr = EffectLinkEffects(eAtkDecr, eSkillDecr);
    eAtkDecr = SupernaturalEffect(eAtkDecr);
    eAtkDecr = SetEffectSpellId(eAtkDecr, SPELLID_LIGHTPENALTY);

    ApplyEffectToObject(DURATION_TYPE_PERMANENT, eAtkDecr, oTarget);

    DelayCommand(0.5f, AssignCommand(oTarget, PlaySound("sfx_drain", TRUE)));
    DelayCommand(0.5f, SetNoticeText(oTarget, "You are experiencing " + sFeat));

    SendMessageToPC(oTarget, "<c=firebrick>You are experiencing " + sFeat
        + "\nAttacks, Saves and Skills are reduced by " + IntToString(iPenalty) + "</c>");
}

////////////////////////////////////////////////////////////////////////
// Initializes oTarget's penalty-type and removes the feat itself.
// - returns -1 if not applicable
////////////////////////////////////////////////////////////////////////
int Initialize(object oTarget);
int Initialize(object oTarget)
{
    int iPenalty;

    if (GetHasFeat(FEAT_LIGHT_SENSITIVITY, oTarget))
    {
        FeatRemove(oTarget, FEAT_LIGHT_SENSITIVITY);
        iPenalty = PENALTY_SENSITIVITY;
    }
	
    else if (GetHasFeat(FEAT_LIGHT_BLINDNESS, oTarget))
    {
        FeatRemove(oTarget, FEAT_LIGHT_BLINDNESS);
        iPenalty = PENALTY_BLINDNESS;
    }
    
	else
	{
        iPenalty = PENALTY_NOTAPPLICABLE;
	}

    SetLocalInt(oTarget, VAR_PENALTY, iPenalty);
    return iPenalty;
}

////////////////////////////////////////////////////////////////////////
// Restores the proper feat if oTarget gets DaylightAdaptation.
// - note that delevel could again wipe off DaylightAdaptation
////////////////////////////////////////////////////////////////////////
void RestoreFeat(object oTarget, int iPenalty);
void RestoreFeat(object oTarget, int iPenalty)
{
    int iFeatId;
    switch (iPenalty)
    {
        case PENALTY_BLINDNESS:   iFeatId = FEAT_LIGHT_BLINDNESS;   break;
        case PENALTY_SENSITIVITY: iFeatId = FEAT_LIGHT_SENSITIVITY; break;

        default: return;
    }

    FeatAdd(oTarget, iFeatId, FALSE);

    if (GetLocalInt(oTarget, VAR_STATE) == STATE_DAY)
        ClearPenalty(oTarget, iPenalty);

    DeleteLocalInt(oTarget, VAR_STATE);
    DeleteLocalInt(oTarget, VAR_PENALTY);
}

////////////////////////////////////////////////////////////////////////////////////////
//'fix_lightblind' travus 2019 feb 11 kevL 2019 feb 12 - modified.
// Fixes the bugs wrt/ LightBlindness (GrayOrc/Drow) or LightSensitivity (GrayDwarf) by 
// removing the feat altogether and applying scripted effects. If the character acquires 
// DaylightAdaptation the proper feat is restored since DaylightAdaptation not bugged.
// NOTE: Is intended as a heartbeat script and currently is coded only for the FirstPC.
// LightBlindness/LightSensitivity is assumed to be a racial feat that a character will 
// have from its creation. (ie, if either feat is granted to a character during play this 
// will not work; because VAR_PENALTY has already been set to PENALTY_NOTAPPLICABLE)
////////////////////////////////////////////////////////////////////////////////////////
void FixLightBlind(object oTarget);
void FixLightBlind(object oTarget)
{		
    int iPenalty = GetLocalInt(oTarget, VAR_PENALTY);
	
    if (iPenalty != PENALTY_NOTAPPLICABLE)
    {
        if (!GetHasFeat(FEAT_DAYLIGHT_ADAPATION, oTarget))
        {
            if (iPenalty == PENALTY_INIT){iPenalty = Initialize(oTarget);}

            int iState = GetLocalInt(oTarget, VAR_STATE);

            if (GetIsNight() || GetIsAreaInterior(GetArea(oTarget)))
            {
                if (iState == STATE_DAY)
                {
                    SetLocalInt(oTarget, VAR_STATE, STATE_NIGHT);
                    ClearPenalty(oTarget, iPenalty);
                }
            }
			
            else if (iState == STATE_NIGHT)
            {
                SetLocalInt(oTarget, VAR_STATE, STATE_DAY);
                ApplyPenalty(oTarget, iPenalty);
            }
        }
		
        else if (iPenalty != PENALTY_INIT)
        {
            RestoreFeat(oTarget, iPenalty);
        }
    }
}