• New Horizons on Maelstrom
    Maelstrom New Horizons


    Visit our website www.piratehorizons.com to quickly find download links for the newest versions of our New Horizons mods Beyond New Horizons and Maelstrom New Horizons!

Need Help Looking at the Blade Damage

Levis

Find(Rum) = false;
Staff member
Administrator
Creative Support
Programmer
Storm Modder
So I want to make it easier for a player to determine how much damage he will output with a certain character because the damage is determined by more than just your sword. But looking at the code I see some things which I don't completly get why they are happeing.
For now I'm just looking at blade damage, let me explain here what is happening first, then we can discus if all of these steps need to be taken or if perhaps steps needs to be added or removed.

The blade damage is applied and calculated in this function. I have removed some of the commented code to make it easier to read:
Code:
void LAi_ApplyCharacterBladeDamage(aref attack, aref enemy, float attackDmg, float hitDmg, bool isBlocked)
{
    //Если неубиваемый, то нетрогаем его
    if(LAi_IsImmortal(enemy)) return;
    isBlocked = isBlocked && CheckAttribute(enemy,"equip.blade") && GetCharacterEquipByGroup(enemy,BLADE_ITEM_TYPE) != "bladeX4";
    // PB: Disable blocking for enemies with either no sword or their fists equiped
    if(isBlocked)
    {
        //Вероятность пробивки
        float p = LAi_BladeFindPiercingProbability(attack, enemy, hitDmg);

        if(rand(10000) > p*10000)    // if block is NOT pierced
        {
            // LDH rewrite 06Apr09
            if(GetCharacterEquipByGroup(attack, BLADE_ITEM_TYPE) == "bladeX4")    // if attacker uses fists, he gets damaged instead
            {
                if(!LAi_IsImmortal(attack))
                {
                    LAi_ApplyCharacterDamage(attack, 2*rand(GetCharacterSkill(attack, SKILL_FENCING)));    // LDH 0..20
                    LAi_CheckKillCharacter(attack);
                }
            }
            return;
        }
    }
In this part the function determines if the the attack is blocked, and if it's blocked it will determine if it's pierced.

The piercing chance is calculated by this formula:
Code:
float LAi_BladeFindPiercingProbability(aref attack, aref enemy, float hitDmg)
{
    float piercing = 0.05;
    float block = 0.01;
    if(CheckAttribute(attack, "chr_ai.piercing"))
    {
        piercing = stf(attack.chr_ai.piercing);            // 0..1 from blade stats
    }
    if(CheckAttribute(enemy, "chr_ai.block"))
    {
        block = stf(enemy.chr_ai.block);                // 0..1 from blade stats
    }
    float aSkill = LAi_GetCharacterFightLevel(attack);    // fencing skill / 10
    float eSkill = LAi_GetCharacterFightLevel(enemy);
   
    // LDH - 01May09
    float p = (1.0 + piercing - block) * (1 + askill - eskill); // Levis: Simplified

    float k = 1.0;
    if (p > 0)                                            // LDH added test 06Apr09
    {
        if(IsCharacterPerkOn(enemy, "BasicDefence")) k = 0.75;
        if(IsCharacterPerkOn(enemy, "AdvancedDefence")) k = 0.5;
    }
   
    //Moved from attack function
    float pBreak = 1.0;
    if(IsCharacterPerkOn(attack, "SwordplayProfessional"))
    {
        pBreak = pBreak + 0.25;
    }
    if(IsCharacterPerkOn(attack, "Rush"))
    {
        pBreak = pBreak + 0.5;
    }
    if(IsCharacterPerkOn(enemy, "Rush"))
    {
        pBreak = pBreak + 0.9;
    }
   
    hitDmg = 0.25*hitDmg^2 + 0.5*hitDmg + 0.25;                            // hitDmg is a small number, so this is 0.5 to 0.52 typically, can be higher

    //Levis: Opium sickness
    float s = 1.0;
    if(CheckAttribute(enemy,"quest.opium_use.opiumsickness")) s = s - stf(enemy.quest.opium_use.opiumsickness);
    if(s < 0.1) s = 0.1;
   
    p = p*k*hitDmg*pBreak*s;
    if (p>1.0) p = 1.0;
    return p;
}

First it take a base value for the piercing and block chance (one is from the attacker and one from the defender). Then it will take the values from their respective weapons and set these.
Now it determines the fightlevel. This fightlevel is one of the mysteries in this code. It is calculated this way:
Code:
float LAi_GetCharacterFightLevel(aref character)
{
    //Fencing skill
    float fgtlevel = 0.0;
    if(isBoardingLoading == false)
    {
        if(CheckAttribute(character, "skill.Fencing") != 0)
        {
            fgtlevel = makefloat(CalcCharacterSkill(character,SKILL_FENCING)); // NK
        }
    }else{
        fgtlevel = CalcCharacterSkill(character, SKILL_FENCING);
    }
    //Level
    if(fgtlevel < 0.0) fgtlevel = 0.0;
    if(fgtlevel > SKILL_MAX) fgtlevel = SKILL_MAX;
    fgtlevel = fgtlevel/SKILL_MAX;
    return fgtlevel;
}
From what I can see the idea is to get a normalized value for the fencing skill in float.
So say the attacker has a fencing skill of 8 it will return a value of 0.8, and if the defender has a fencing skill of 6 it will return a value of 0.6.

Going back to the piercing function it now takes these values and inputs them in the formula:
p = (1.0 + piercing - block) * (1 + askill - eskill);

The block and piercing values are floats, say the attacker has a piercing chance of 15% while the defender has a block chance of 60% then the formula will result in:
p = (1,0 + 0.15 - 0.60) * (1 + 0.8 - 0.6)
p = (0.55) * (1.2)
p = 0.66

Now the function will check some of the perks.
The k will be set to 1.0, 0.75 or 0.5 depending on which defense perk the defender has. Say he has the basic defense, so k will be set to 0.75

Now the perks for the attacker are determined. If swordplayprofessional is in posesion of the attacker the pBreak will be increased to 1.25, if rush is enabled it will be increaed with another 0.5, if the enemy has rush active it will be increased with another 0.9 (because rush lowers your ability to block).

Now there is a mystery for me again. The hitdmg is manipulated with this formula:
hitDmg = 0.25*hitDmg^2 + 0.5*hitDmg + 0.25;
This hitDmg is the damage which is send by the engine itself, so I guess this is based on the damage values by the sword, but I'm not 100% sure. According to the comments it's somewhere betwee 0.5 and 0.52, and taking a value of 0.5 in this function will result in a value of 0.5625

In the last part the opium sickness is taken into account, this will results in a float value between 1.0 and 0.1, where 1.0 is not sick while 0.1 is very sick. Say for our example the enemy doesn't have opium sickness so the value is 1.

At the end the chance is calculated by:
p = p*k*hitDmg*pBreak*s

Say we take the values we had before we get:
p = 0.66*0.75*0.6525*1
p = 0.323...

So about 1/3 of the blocked attacks will be pierced. This does sound about right, but escpecially the hitDmg here I'm not sure about. I think the idea here is that a higher hit will be easier to pierce than a lower hit. But is this really what we want?

After we determined if the attack is pierced or not it either returns or applies damage to the character if it's fighting with fists.

If the attack isn't blocked or if the attack is pierced we go on with the next part:

Code:
    //Вычисляем повреждение
    float dmg = LAi_BladeCalcDamage(attack);
    float damage = LAi_BladeApplySkills(attack, enemy, dmg);

Here the damage from the blade is determined and skills are applied to the damage. This is done by this formula:
Code:
float LAi_BladeApplySkills(aref attack, aref enemy, float dmg)
{
    //Учитываем скилы
    float aSkill = LAi_GetCharacterFightLevel(attack);
    float eSkill = LAi_GetCharacterFightLevel(enemy);
    if(aSkill >= eSkill)
    {
        dmg = dmg*(1.0 + 2.0*(aSkill - eSkill));
    }else{
        dmg = dmg*(1.0 + 0.5*(aSkill - eSkill));
    }
    // NK -->
    dmg *= (0.75 + fRand(CalcCharacterSkill(attack, SKILL_FENCING))/(40.0/3.0));
    dmg *= (1.5  - fRand(CalcCharacterSkill(enemy, SKILL_FENCING))/(40.0/3.0));
    float piercing = 0.05;
    float block = 0.01;
    if(CheckAttribute(attack, "chr_ai.piercing"))
    {
        piercing = stf(attack.chr_ai.piercing);
    }
    if(CheckAttribute(enemy, "chr_ai.block"))
    {
        block = stf(enemy.chr_ai.block);
    }
    if(piercing > block*2 && piercing > 0.1) dmg *= (1.0 + fRand(piercing)/2.0);
    if(GetDifficulty() < 2)     //TY Commenting out old difficulty mod, HP changes and such are better for balancing difficulty while preserving meaningfulness of hp and damage numbers given to players, and a more forgiving landlubber
    {
        if(sti(attack.index) == GetMainCharacterIndex()) dmg *= 1.3; 
        else { if(sti(enemy.index) == GetMainCharacterIndex()) { dmg *= 0.5; } } 
    }
    // NK <--
    //Moved from attack function
    float kDmg = 1.0;
    if(IsCharacterPerkOn(attack, "Rush"))
    {
        kDmg = 1.5;
    }
    dmg = dmg*kDmg;
    //Аттака своей группы
    kDmg = 1.0;
    if(IsCharacterPerkOn(enemy, "BasicDefence")) kDmg = 0.9;
    if(IsCharacterPerkOn(enemy, "AdvancedDefence")) kDmg = 0.8;
    if(IsCharacterPerkOn(enemy, "SwordplayProfessional")) kDmg = 0.7; // Baste
    //Levis: Opium sickness
    float s = 1.0;
    if(CheckAttribute(enemy,"quest.opium_use.opiumsickness")) s = s - stf(enemy.quest.opium_use.opiumsickness);
    if(s < 0.1) s = 0.1;
    dmg = dmg*kDmg*s;
    return dmg;
}

Again we start with the fightlevel determination. If the attacker has a higher skill then the defender the damage is boosted, while else the damage is reduced.
this means your swords will get better the higher your level becomes but it is also determined later by a multiplier determined on your fencing skills, so effectivly it's done twice.
After that the piercing and blocking attributes are determined again and if piercing is higher then twice the block value it will increase the damage with a random value.
Personally I don't see much reason for this. And else we should check if the attack is really pierced and just make a pierced attack a crit or give it extra damage, but not have a random number here influence the attack power.
Now a the attack power is modified based on the difficulty. in the comment is sugest to comment this out, but for now this hasn't happened yet. For now it only differentiates between 0,1 and 2 and higher. I think there is room for improvement on the difficulty aspect for sure and would love to hear feedback from
Next up several perks are checked, First if Rush is activated the damage will be multiplied by 1.5. Afterwards the enemies defense perks are checked and the opium sickness and depending on this the damage is reduced.
Looking at this again I think the opium sickness is reversed here and it should increase the damage you get instead of bring it down right?

Code:
    // Baste - critical hit calculation changed -->
    float critical = 0.0;
    if(IsCharacterPerkOn(attack, "SwordplayProfessional"))
    {
        if(rand(100) <= 25)
        {
            critical = damage*2.0;
        }
    }
    else if(IsCharacterPerkOn(attack, "CriticalHit"))
    {
        if(rand(100) <= 10)
        {
            critical = damage*2.0;
        }
    }
    else
    {
        if(rand(100) <= 5)
        {
            critical = damage*2.0;
        }
    }
    if(isBlocked)
    {
        damage = damage*0.3;
        critical = critical*0.3;
    }

Next up the critical change is calculated. Personally I would do this the same as it happens with guns, where there is function to determine the chance of critting (which then can also be communicated to the player) and when it crits certrain things happen.
Here also the code uses an else if statement which I believe doesn't work in the potc code so I'm not sure how well this code actually works.
Also here the damage will be increased if the attack is piercing a block so this doesn't have to be done earlier.


Code:
    bool noExp;        // PB: Whatever is on this line ends up changing from what you set. Positively BIZARRE behaviour!
    noExp = false;    // Baste: So we set it to false on THIS line instead
    if(CheckAttribute(attack, "chr_ai.group"))
    {
        if(CheckAttribute(enemy, "chr_ai.group"))
        {
            bool AttackEnemyGroup = attack.chr_ai.group == enemy.chr_ai.group;
            bool PlayerTrainingFight = CheckAttribute(GetMainCharacter(),"TrainingFight");
            bool AttackTrainingFight = HasSubStr(attack.id,"TrainingFight_");
            bool EnemyTrainingFight  = HasSubStr( enemy.id,"TrainingFight_");
            if(AttackEnemyGroup && !PlayerTrainingFight && !AttackTrainingFight && !EnemyTrainingFight)//MAXIMUS
            {
                damage = 0.0; // dmg = 0.0;
                critical = 0.0;
                noExp = true;
            }
        }
    }
Here things for training fights are determined. Notice this will be done at every sword attack. I believe the training fight already has a check also where it protects people from dieing so I don't know why this should be here.
Code:
    if(critical > 0.0)
    {
        if(sti(attack.index) == GetMainCharacterIndex())
        {
            Log_SetStringToLog(XI_ConvertString("Critical Hit"));
        }
    }
This line should be moved to the other check for critical hit. it will reduce checks which have to be done and clears up the code.
Code:
    // Baste <--
    // TIH --> do not show XP messages for other characters, its annoying! Aug24'06
    bool resetshowXP = false;
    if(sti(attack.index) != GetMainCharacterIndex())
    {
        attack.donotshowXP = true;
        resetshowXP = true;
    }
Why is the dontotshowXP attribute set here for a character while there is also a boolean to disable this already?
Code:
    // TIH <--

    //Наносим повреждение
    // Baste -->
    if(critical > 0.0)
    {
        LAi_ApplyCharacterDamage(enemy, MakeInt(ApplyArmor(enemy, attack, critical, true) + 0.5)); // GreatZen-NK
    }
    else
    {
        LAi_ApplyCharacterDamage(enemy, MakeInt(ApplyArmor(enemy, attack, damage, true) + 0.5)); // GreatZen-NK
    }
Here we check if the attack is a crit or not. Personally I'd say we should have a critchance and only here determine if it's a crit or not based on a random number, instead of how it went before.
Also based on the skills etc there should be a multiplier for the crit. Or maybe this should be a fixed multiplier. Now if I read the code right the damage is multiplied by 2 (at least thats what it should do) if it's a crit and if the attack pierces it will be dived by 3.
Code:
    //Проверим на смерть
    LAi_CheckKillCharacter(enemy);
    //Есть ли оружие у цели
    bool isSetBalde = (SendMessage(enemy, "ls", MSG_CHARACTER_EX_MSG, "IsSetBalde") != 0);
    //Начисляем опыта
    if(critical > 0.0) damage = critical; // So EXP calculation takes critical damage into account
    float exp = LAi_BladeCalcExperience(attack, enemy, damage);
If we have the multiplier etc we wouldn't need the extra crit check here also. I'm not going to look into the blade xp now, I believe we looked into that before already.
Code:
    // Baste <--
    if(LAi_IsDead(enemy))
    {
        //Начислим за убийство
        exp = exp + LAi_CalcDeadExp(attack, enemy);
        if(!isSetBalde)
        {
            // ccc mar05 REPLOSS tweak added
            if(enemy.chr_ai.group != LAi_monsters_group)
            {
                if(sti(attack.index) == GetMainCharacterIndex()) LogIt("CHANGE REP for player: " + -REPLOSS*3 + " - undrawn blade 1");     // LDH 19Dec08
                LAi_ChangeReputation(attack, - REPLOSS*3); // NK tempfix for un-drawn blades 04-17
                if(IsMainCharacter(attack)) GuardsAttackOpium(); //All near guards will attack you now. -Levis
            }
        }
    }
    if(!isSetBalde)
    {
        // ccc mar05 REPLOSS tweak added
        if(enemy.chr_ai.group != LAi_monsters_group)
        {
            if(sti(attack.index) == GetMainCharacterIndex()) LogIt("CHANGE REP for player: " + -REPLOSS + " - undrawn blade 2");     // LDH 19Dec08
            LAi_ChangeReputation(attack, - REPLOSS); // NK tempfix for un-drawn blades 04-17
        }
        exp = 0.0;
    }
This part seems to be okay from what I see, but maybe others want to comment on it.

Code:
    if(!noExp) { if(AUTO_SKILL_SYSTEM) { AddCharacterExpChar(attack, SKILL_FENCING, MakeInt(exp*0.5 + 0.5)); }else{ AddCharacterExp(attack, MakeInt(exp*0.5 + 0.5)); } }
technically this could be change to just 1 function instead of the if because the leveling script will take care of it now.
Code:
    if ( resetshowXP ) { DeleteAttribute(attack,"donotshowXP"); } // TIH do not show other characters XP increases
}
I have no idea why this line is needed...
Those are my comments on the blade damage stuff. I'm curious what you guys think.
 
You'll have no difficulty convincing me that the code is quite a mess. Looks like I ran into insanity there in the past already:
Code:
PB: Whatever is on this line ends up changing from what you set. Positively BIZARRE behaviour!
But I don't know if it is wise to try and clean it up. It technically does function now.... :unsure
 
Back
Top