New Formulae & Game Mechanics (big update, 26th June)

Information and updates for all other projects by Black Omen Productions (besides Shadow Empires): Namely Codename: MetalStorm, Anti-Balance, Ultimate Fixer and all versions of XyRAX

Moderators: Nefarius, Joel, Doombreed-x, Vendanna

Post Reply
User avatar
Nefarius
Retired Admin
Cherub
Posts: 11607
Joined: Sat Jun 15, 2002 8:13 pm
Location: Where the blood forever rains
Contact:

Hand-picked

New Formulae & Game Mechanics (big update, 26th June)

Post by Nefarius » Mon Jan 07, 2008 4:17 am

In this topic formulae are collected which are currently implemented in a hopefully final form.



Life, Mana and Stamina steal: These stats are much more working in the way they used to before they were nerfed so badly, otoh their availability is somewhat lower ( and don't expect dual leech on everything, this is only possible on the items that traditionally could get it, higher end rare jewelry and weapons ), stamina steal is a bit of an exception as the stat, while not being new (it didn't work when used by players in vanilla), is now a third leech factor and stamina generally being more important (stamina determines whenever you go into hit recovery mode, whenever you can do anything whatsoever*). So whats different about these, there is no longer a global penalty, this means 3% ll on normal are 3% ll everywhere. Monsters have individual resistances to draining effects. Especially, life and stamina steal will not work against any undead creature (undead being dead, stealing life from them makes no sense). Mana/Life/Stamina steal are cancelled by curses and status alterations like Prevent Heal (life), Prevent Regeneration (mana) and Prevent Recovery (stamina).

* every spell and ability you use consomes a small amount of stamina, alongside the regular cost ( which may be a cost to life or mana, some things will also directly use stamina ), getting low on stamina means you become fatigued, this has side effects, for example you will get stunned by attacks instead of going into hit recovery for a few frames, the less stamina you have, the more often you get knocked into hit recovery too ( ie the fuller your bar is, the less likely it is a suffered hit will interrupt you ). Stamina is also lost when you take a beating so beware. ( stamina is thus just what stamina is meant to be, your overall endurance ). The amount of stamina you have will (in the future) also influence how much damage poison and various diseases will induce per frame. Take note of the fact that the base run/walk speeds are back at their >CLASSIC< values, ie higher then the lod values. Having equipment that lowers stamina drain and improves stamina recovery allow you to recover stamina even while running.

---

No drop evaluation, the Neph way. D2 uses a extensive floating point formula for this, its not really necessary as similar results can be achieved without.

First it retrieves the current player count and the party count ( being partied players in the killers party that are alive and in the same level ). For minions, it ofc uses the minion owner for this.

Both values are capped to a range of 1-8 (notice: MetalStorm allows for #players command to go up to 128, this is capped only in the drop eval, the other boni are documented below). Notice that we could easily allow it to go higher (it doesn't use the vanilla variable), but I feel #players 65K would be retarded.

Next it computes the reduction index, being (players / 2) + party. The result is a value between 1 and 12.

This value is used to lookup the reduction in a table. These reduction values are in 1024th precision.

1 = 1024
2 = 819
3 = 655
4 = 524
5 = 419
6 = 335
7 = 268
8 = 214
9 = 171
10 = 137
11 = 109
12 = 87

In a 8 player game with a 8 player party, you would recieve nodrop chance of nd*87/1024. IE nodrop of 20 would become a nodrop of 1 (etc). For a solo 8 player game, you recieve a nodrop chance of 419/1024 (ie 40% of the 1 player value).

Notice that the nodrop in MetalStorm is low from the start, (you get something from about every 2nd unit, not every 5 or so units like in vanilla) at #players 1.


Other #players values: (player count is taken as n - 1)

Crushing Blow Penalty: n*60
Damage bonus: (n*200)/(n+200)
Attack rating: n*40
Defense: n*50
Life: n*60
Exp: n*40
Skill Level: n/2
Chance for undead spawning from chests: n*5 (for stuff like barrels)
Speed bonus: (n*100)/(n+100) - currently disabled



Openwounds: there are several considerable changes done to this stat to make it less abusive, while not making it useless. (IE. it still has potential for awesome damage, but not as easily and abusively as in D2).

First of all, OW can only effect units that are capable of bleeding, which excludes a large portion of the undead, units that bleed acid may be damaged by this too. A unit already afflicted by open wounds cannot be afflicted by it again until the first effect expires.

The damage dealt by open wounds depends on how much physical damage your attack has dealt. (Thus it will not work against physical immunes, unless you used a item/spell that bypassed their resistances). When OW is applied vs. players (inc. hirelings) the damage is divided by 2, when used against bosses, it is divided by 4. Finally, if it is applied by a ranged attack, the result of the former is AGAIN divided by 2.

The effect lasts for a random duration between 5-10 seconds.
(direct result: you need to hit hard to get a good damage output from this, as it isn't level based anymore and can be resisted).


Stone Curse: Some items have a chance to petrify units when struck, this follows the regular stonecurse rules, the duration varies from 1-2 seconds (there is no difficulty level penalty on this).


Doppelgaengers: this stat will clone the target you hit x % of the time.

Things that cannot be cloned: players, hirelings, allies (of any sort), invicible creatures, npcs, stuff that was already cloned, quest and boss units (inc. champions).

The clone created has 50% of the 'source' units life, does not give exp nor drop items. It will activate after 2 seconds (ie it stalls for 2 seconds).


Disintegrate Lower Undead: this effect is found on some weapons, its mostly a "screen declutter" stat, it lets you remove lower undead (zombies, skeletons [not all mages]) from existance in a single blow. With a price, they disintegrate, which incs. their exp and drops.


Crushing Blow: Crushing blow works similar to vanilla, with a bug fix and changed values. First of all, at the very base (no resistances etc), crushing blow removes 1/4th of a monsters life, 1/6th of a player/hirelings life and 1/8th of a bosses life. When the effect is applied by a missile, it is half as effective (ie 1/8th for monster, 1/12th for player/hireling and 1/16th for bosses). Next depending on the # of players in the game, the effect is penalized by div*(60*n-1)/100, this means for 8 players, it removes 1/16th of a monsters life (do the math for others yourself :P). Finally, the amount of damage actually applied in the end is subject to physical resistance (players cannot become immune here like they could in vanilla, well they _CAN_ become immune as its now allowed to become immune, but you get the idea, it calls the same routine the normal resistance application does, so its governed by the same rules as regular attacks are).

---

In this topic formulae are collected which are currently implemented in a hopefully final form.

Kicking Damage

Due to the massive amount of math involved, im posting the code itself here.

Code: Select all

void SKILLS_EvalKickDamage(D2UnitStrc* pUnit, D2GameStrc* pGame, D2DamageStrc* pDamage, D2UnitStrc* pTarget, int skillNo, int sLvl)
{
   // assigns the kick damage to the damage structure
   // this is what generally controls how much damage
   // all of the kick skills end doing
   // REVISED 6th June, 2008 (by Nefarius)

   ASSERT(pUnit);


   // apply progressive damage percent

   SKILLS_GetProgPhysDamBonus(pUnit, pDamage);

   pDamage->HitFlags |= (HITFLAG_DAMAGEDONE|HITFLAG_SRCMISSILE);


   // get the base min and max damage, as well as the damage percent
   // this here evaluates the actual kick damage

   int nDmgPct         = pDamage->dPercent;
   int nKickDmg      = DAMAGE_MakeSkillPhysDmg(pUnit, skillNo, NULL, sLvl) >> 8;
   int nKickMinDmg      = 0;
   int nKickMaxDmg      = 0;
   int nKickPct      = 0;

   DAMAGE_GetKickDamage(pUnit, &nKickMinDmg, &nKickMaxDmg, &nKickPct);

   nKickDmg += ApplyPct
            (
               pUnit->unitSeed.accrand(nKickMinDmg, nKickMaxDmg),
               100,
               nKickPct
            );

   nKickDmg += ApplyPct(nKickDmg, 100, nDmgPct);
   nKickDmg = ((nKickDmg <= 1) ? 1 : ((nKickDmg >= 8388607) ? 8388607 : nKickDmg));

   pDamage->dPhysical += nKickDmg << 8;
   pDamage->dPercent = 0;


   // add elemental damage and length from skills.txt

   DAMAGE_MakeSkillElemDmg(pUnit, skillNo, pDamage, sLvl);

   // set item states, this is important to not
   // grab stats from the weapons

   D2UnitStrc* pWeapon = DAMAGE_SetWeaponStates(pUnit, pUnit->ptrInventory);

   // generally apply damage from charms (etc)

   DAMAGE_ApplyMeleeDamage(pGame, pUnit, pTarget, pDamage, 1, 128);


   // add progressive skill elemental boni

   SKILLS_GetProgElemBonus(pUnit, pDamage);


   // reset active state on main weapon

   if (pWeapon)
   {
      D2SetItemActive(pUnit, pWeapon, 1);
   }
}

// *** KICK DAMAGE **************************************************

int __fastcall DAMAGE_GetSkillKickDamage(D2UnitStrc* pUnit)
{
   // returns the base damage dealt by kick skills
   // this returns unshifted damage, so apply the shift in
   // the skill functions please!
   // REVISED 6th June, 2008 (by Nefarius)

   ASSERT(pUnit);

   int nDamage;

   switch (pUnit->eType)
   {
      case UNIT_PLAYER:
      {
         // for players, damage is
         // equal to dex * 2 + str

         int nDex = D2GetStat(pUnit, STAT_DEXTERITY);
         int nStr = D2GetStat(pUnit, STAT_STRENGTH);
         nDamage = (nDex * 2) + nStr;
         break;
      }

      case UNIT_MONSTER:
      {
         // for monsters, damage is
         // equal to mLvl * 5

         nDamage = COMMON_GetUnitLevel(pUnit) * 5;
         break;
      }

      default: return 0;
   }

   nDamage = ((nDamage <= 1) ? 1 : ((nDamage >= 8388607) ? 8388607 : nDamage));

   return nDamage;
}

void DAMAGE_GetKickDamage(D2UnitStrc* pUnit, int* pMinDmg, int* pMaxDmg, int* pDmgPct)
{
   // returns the kick damage and percent enhanced damage
   // for this unit, this is equal to D2Common.10747
   // REVISED 6th June, 2008 (by Nefarius)

   ASSERT(pUnit);

   int nMinDmg = 0;
   int nMaxDmg = 0;
   int nDmgPct = 0;

   int nKickDmg = D2GetStat(pUnit, STAT_ITEM_KICKDAMAGE);

   D2InventoryStrc* hInv = pUnit->ptrInventory;
   D2UnitStrc* pWeapon = DAMAGE_SetWeaponStates(pUnit, hInv);

   D2UnitStrc* pBoots = D2GetEquippedItem(hInv, bLOC_BOOTS);

   if (CheckItem(pBoots))
   {
      ItemsTXTStrc* pItemsTxt = D2GetItemRecord(pBoots->nIndex);

      ASSERT(pItemsTxt);

      nMinDmg = pItemsTxt->MinDmg;
      nMaxDmg = pItemsTxt->MaxDmg;
      nDmgPct += DAMAGE_AddStatItemBonusInline(pUnit, pItemsTxt->StrBonus, STAT_STRENGTH);
      nDmgPct += DAMAGE_AddStatItemBonusInline(pUnit, pItemsTxt->DexBonus, STAT_DEXTERITY);
   }

   nDmgPct += D2GetStat(pUnit, STAT_DAMAGEPERCENT);
   nDmgPct += D2GetStat(pUnit, STAT_ITEM_MAXDAMAGE_PERCENT);
   nDmgPct = (nDmgPct <= -100) ? -100 : nDmgPct;

   *pDmgPct = nDmgPct;
   *pMinDmg = nMinDmg + nKickDmg;
   *pMaxDmg = nMaxDmg + nKickDmg;

   if (pWeapon)
   {
      D2SetItemActive(pUnit, pWeapon, 1);
   }
}
---

Changes to "poison" damage

The category poison damage currently evaluates to four damage types (Poison, Burning, Acid and Open Wounds), in vanilla these things simply drained your hitpoints, with no other side effects other then a graphical effect (which in the case of open wounds doesn't work on players). Acid being one discussed further below in this topic, now also dissipates your items durability faster (the initial blow from acid also crapifies durability).

Now they have a different side effect that was not yet mentioned elsewhere, when you are effected by a draining damage type, every 25 frames you are without exception knocked into hit recovery (may be changed to 50-75 on lower difficulty settings in the future, not sure right now). (also, you can die from these things now). So keep yourself a stock of antidote potions or else (for poison), there will be ways to ward yourself from the other things as well just so you know.

---

Attacker vs. Defender Proc

If the defender is frozen or stone cursed, the chance to hit the defender is 100%.


First getting defense:

BaseDefense = DEX * 5 + AC + (ShieldAC * HS / 100);

If you have Holy Shield active, the defense bonus of the skill is applied extra to the shields defense (like it used to be before it got borked [actually it is applied twice in vanilla v1.10 and just doesn't display...]).

DEF = Max((BaseDefense * (100 + AcPct) / 100) * (100 - AcOverride) / 100, 0) + AC_VS_ATTACK + AC_VS_MONTYPE;


AcPct is the sum of all +% defense (skills and non-armor items), AcOverride is an override effect altering total defense (like in vanilla Berserk does). AC_VS_ATTACK stands for AC vs. Missile/Melee. AC_VS_MONTYPE refers to Defense vs. Monster Type, which is a stat that was never finished for vanilla.

Next, getting attack rating.

BaseAttack = Max(ATT + (DEX * 5) * FACTOR / 100, 0);

FACTOR represents a tohit factor, in vanilla this factor is a mere +# thing, it was changed by us to a percentage based alteration, so that it effects the overal chance tohit at all times (it only effects tohit obtained from DEX, not item based tohit).


Now the ATT_BONUS has to be evaluated:

ATT_BONUS = MASTERY_ATT_PCT + ATT_PCT_VS_MONTYPE + SKILL_BONUS + ITEM_TOHIT_BONUS

Mastery attack rating percent is clear, vs. montype is also clear I guess, these are both vanilla stats. SKILL_BONUS refers to the +% tohit granted by skills (the bonus from skills.txt, not for stats), ITEM_TOHIT_BONUS refers to +% Attack Rating.


Finally, getting the final tohit:

ATT = BaseAttack * (100 + ATT_BONUS) / 100

Now, special alterations are done to ATT and DEF.

ATT = ATT + ATT_VS_HIGHER_TYPE
DEF = (DEF + TARGET_DEFENSE + DEF_VS_HIGHER_TYPE) * (100 - Max(TARGET_DEFENSE_PCT, 0)) / 100

TARGET_DEFENSE refers to the, in vanilla bugged, -Target Defense modifier, which ends up being permanent there, here it works like it should. TARGET_DEFENSE_PCT is -% Target Defense, which was bugged back in v1.09 and earlier but fixed in v1.10. Finally, if you have IGNORE_TARGET_AC, it has been changed to a chance to ignore the entire defense, rather then being a boolean. In any event, if the roll succeeds, DEF is set to 0.


Now before progressing any further, DEF and ATT are limited to positive values (if they are negative they are set to 0). If the defender is CHILLED or STUNNED then DEF is cut in half (the effects are cummulative, so if both conditions apply DEF is effectively divided by 4).

If the attacker is CHILLED, ATT is cut in half (you can't attack while stunned so theres no penalty for that).

Finally, the actual 'gethit roll' takes place. This no longer cares about unit levels, all it bothers with is ATT and DEF, for this little reason you actually have a reason to care about these stats now and can't just rely on your clvl to do the trick like vanilla.

CHANCE = max(min((ATT * 100) / (ATT + DEF),95),5);

(if you wonder, this is the vanilla formula with just the lvl thing taken out).

Blocking and stuff like this will be documented at some later date.

---

Player Resistances (normal ones)
* Note Negative Plane and Acid resistances have seperate routines, this here applies to Fire/Cold/Light/Magic/Poison/PoisonLength/Physical resistances - cold resistance also reduces the length of freeze and chill effects.

The maximum resistance a player can achieve is 100% (immunity) against all damage types except physical (which is capped to 50% except when certain spells are employed). Yet to become immune to an element requires a lot of effort and equipment sacrifice, resistances (in large quantities) are scarce, and prismatic effects are only found on magic items.

The below isn't evaluated and set for monsters and set to 100 (except for hirelings, which follow player rules)

MaxRes = max(min(75 + MaxRes, 100), 0);


If the GotterDammerung effect (named after that D1 unique) takes place, the code sets MaxRes to 0 (except for physical resistance).

Next the ResPenalty has to be evaluated

ResPen = min(max(100 - DiffLvlPen + max(BaseRes, 0), 0), 100);

BaseRes refers to the new +to Base Resistances stat, this stat will reduce the penalty, it cannot be used to get the base resistances above 0 however.


Actual Resistances are prone to the Resistance Penalty, Pierce Resistances and limit to the min (-100) and max (see max calc) bounds. The penalty is not applied to monster resistances. Pierce however is, and effects immune monsters fully. The penalty and pierce effects are only applied if the resistances are positive, negative resistances are not altered.

Res = min(max((Res * ResPen / 100) * (100 - Pierce) / 100, -100), MaxRes);


Thus in the case you have 75% fire resistance, on hell (where the resistance penalty amounts 75%) you will have 75 * (100 - 75) / 100 res left (18%). If you're up against an enemy that has a piece stat on him, and it amounts for 75% too, the resulting resistance will be: (75 * (100 - 75) / 100) * (100 - 75) / 100 (aka 4%) etc. For those suffering from dyscalculia, the new penalty system means that you will not end with negative resistances on the higher difficulty levels unless you are effected by a spell reducing them directly. The penality however is a lot harsher then the vanilla penality (for the resistances to increase by 1 point, you need 4 points in the resistance on hell, so if you end having enough +MaxRes to achieve immunity, you need 400% in the resistance to actually become immune on hell).


---

Durability Loss (attack/defend):

When attacking a soft enemy, the chance that a weapon loses durability is 5%, when hitting enemies that aren't soft (skeletons etc) you have a 15% chance to lose durability.

When hit by an enemy (in melee) you have a 10% chance that one of your armors pieces will lose durability (which is chosen randomly). If the monster hitting you is large (blunderbores etc) the chance is higher, at 20%. For missile attacks the chance is 5%.

If you are effected by acid damage, the chance to lose durability on these events is 100% and the amount of durability lost is between 1 and 6 instead of 1.

Throwing Weapons used in melee have a special form of durability, that is if you have a stack of 10 javelins and each javelin has a durability of 2, then you have 20 durability in total before it shatters.

Speaking of that. When an item runs out of durability one of the following things happens: 25% of the time, the item shatters, this means it is gone forever. The rest of the time, it only breaks and can be repaired.

---

Player Experience / Party Sharing:

In vanilla the way experience is shared in a party is rather convulged so I won't go into details here about what once was, this is about whats today icon_wink.gif

First of all, how is regular experience calculated:

clvl = your real clvl (ignoring level drain effect to prevent cheating)
mlvl = the level of whatever you've just killed

Code: Select all

if (cLvl > mLvl) {
   int diff = cLvl - mLvl;
   if (diff > 10)
      malus = (100 * mLvl) / cLvl;
} else if (cLvl < mLvl) {
   int diff = mLvl - cLvl;
   if (diff > 10)
      malus = (100 * cLvl) / mLvl;
}

if (malus < 100)
{
   if (malus < 5)
      malus = 5;

   experience = max(experience * malus / 100, 1);
}

if (stat.addexp != 0)
{
   experience = max(experience + experience * stat / 100, 1);
}

if (rnd(10000) == 0)
{
   experience = experience * 2;
}
---

Missile Distance Penalty:

dist = distance the missile has so far traveled
too close: 7 steps (min 8 steps)
ideal: 8-64
too far: 65 steps (max 128 steps)

Note: this malus is applied to attack rating for 'physical missiles' (arrows etc) and to damage for 'energy missiles' (fire balls etc), when applied to damage the effect is capped to -75 at most, so that it doesn't produce blank projectiles. (tohit chance is capped to 5% at the lowest)

For physical missiles that have a tohit bonus from a skill, the following is done: bonus = bonus + (bonus * malus / 100) + malus.

Code: Select all

if (dist < 8)
{
   if (dist < 0)
      dist = 0;
      return -100 * (8 - dist) / 8;
}
   else
{
   if (dist > 64)
   {
      if (dist > 128)
         dist = 128;

      return -100 * (dist - 64) / 64;
   }
}
Last edited by Nefarius on Thu Jun 26, 2008 4:07 am, edited 22 times in total.
''(...) The game can basically be considered unhackable. '' - Blizzard Entertainment (30th May 2000)
Black Omen Productions | MetalStorm: Progress Report | Screenshots

Post Reply

Return to “Mods by Nefarius”