Friday, August 23, 2019

The not-so-basic mechanics of Wizardry



Around 2012-2014, Thomas William Ewers reverse-engineered the Wizardry code and published compilable Pascal source to Asimov, which was invaluable for understanding not just the mechanics of Wizardry, but also for decrypting the data behind monsters, characters, items, weapons, treasures, and furthermore, understanding what all of those stats actually do.

Parsing the source code still took a lot of effort to gain the understanding that I have, and quite a bit may still escape me. Variable and function names are Ewers’ invention, are terse, and don’t always best reflect what the variable or function does. A lot of logic behaves very strangely by modern standards, with single variables that serve multiple purposes, spaghetti code, and data which gets copied from variable to variable and back. It’s often hard to tell what a thing is supposed to be doing at any given moment. There are some stats and intricacies that are barely used or even not used at all, including logic for encounters that don’t exist in Wiz1, and some very complicated logic in the treasure system that seems to never be invoked by any of the treasures in the game. I’ve glossed over such things, but may return to it when I cover later Wizardries which may or may not involve them.

In many cases, I have chosen my own names to represent variables in the Pascal code, rather than use the names that Ewers chose, which, by his own admission, aren’t always the best names he could have used. For instance, what he calls LUCKSKIL[0], I call “Save vs. Death.” Bear this in mind if you decide to look at the Pascal code yourself and cross-reference it with my notes.

I won’t detail things that are self-explanatory, or that are already described in the original manual.

Character stats

Race not only determines your “base” stats, but it also grants a bonus to a saving throw. I’ll get into saving throws later; they are not mentioned in the manual or ever explicitly acknowledged in the game, but they are part of it.

STR IQ PIE VIT AGI LUC Saving throw
Human 8 8 5 8 8 9 -1 to Death
Elf 7 10 10 6 9 6 -2 to Wand (useless)
Dwarf 10 7 10 10 5 6 -4 to Breath
Gnome 7 7 10 8 10 7 -2 to Petrify
Hobbit 5 7 7 6 10 15 -3 to Spell

Bonus points and gold

When creating a new character, bonus points to assign to stats are typically 1d4+6. However, after these are assigned, there is a 1/11 chance of getting an additional 10 bonus points. And after that, as long as you don’t have 20 points already, there’s a 1/11 chance of getting ANOTHER 10 bonus points.

Approximate odds of bonus point ranges are:

7-10 90.9%
17-19 6.2%
20 2.3%
27-29 0.6%

Initial gold is set to 1d100 +89.

Strength

The only purpose of strength is to affect two hidden stats, “HitCalcMod” and “HitDam,” which affect your melee accuracy and damage. Values below 6 incur a penalty to both, and values above 15 incur a bonus to both. There is essentially no difference between a strength of 6 and a strength of 15. More on this later.

IQ and piety

Whenever you level up, each spell that you are eligible to learn has a chance of (IQ/30) to be learned. For priest spells, this is (Piety/30), of course. The first spell in each circle will always be learned as soon as it is possible to.

Each combat round, each character has a chance of (IQ + Piety + CharacterLevel)/99 to identify a monster group. The monster group identified is randomly chosen from 1-4, and if the monster group selected doesn't exist or has already been identified, then nothing happens.

Vitality

Chances of a successful Di or Kadorto are the target's Vitality * 4%. A success permanently lowers the target's Vitality by 1.

Chances of successfully raising a dead character at the Temple of Cant are (Vitality * 3%) + 50%.

Chances of successfully restoring an ashen character at the Temple of Cant are (Vitality * 3%) + 40%.

If a player's vitality goes below 3 for any reason, they are LOST. If this happens when leveling up, you get the "YOU HAVE DIED OF OLD AGE" message.

When leveling up, high and low vitality grants a maxHP bonus or penalty, which stacks with the class bonus.

VIT Effect
3 -2
4 -1
5 -1
16 +1
17 +2
18 +3

Agility

Each round, each character has an initiative roll of 1d10. Initiative is further modified by agility.

AGI Init. mod
3 +2
4,5 +1
6,7 0
8-14 -1
15 -2
16 -3
17 -4
18 -5

The modified result is clipped to the 1-10 range.

Monsters’ initiatives are each set to 1d8+1. Everyone acts in order of initiative, from lowest to highest.

If you step into a pit, each character has a chance of (Agility - Maze Level) * 4% to avoid damage.

If you Malor into the castle moat, each character has a chance of Agility * 4% to not drown.

Agility affects thievery success rates. More on that later.

Luck

Luck affects your saving throws. More on that in a minute.

Luck also appears to be programmed to do something when you teleport into rock (see the function BREAKPOS in SHOPS2), but I can't figure out what it does or why it would matter at that point.

Saving throws

Saving throws are a nearly invisible game mechanic, never listed on character sheets or explicitly referred to ingame. There are five saving throw types; Death, Petrify, Wand, Breath, and Spell.

Save vs. Death resists the effects of poison, paralysis, and critical hits in combat. It does not resist these effects from traps.

Save vs. Petrify resists the effects of stoning in combat. It does not resist the effect of petrifying traps.

Save vs. Wand does nothing at all!

Save vs. Breath resists breath attacks and gas traps. A successful save against a breath attack cuts the damage in half. A successful save against a gas trap nullifies the effect. It does not resist any spells.

Save vs. Spell resists the effects of the Montino spell, of anti-priest traps, and anti-mage traps. A successful save against Montino nullifies the effect. Priests and mages who save against such traps will be paralyzed (rather than stoned). Samurai and bishops who save against such traps will negate the effect (they are paralyzed in a failed save).

The chance for a character to make a successful saving throw can be expressed as:
(CharacterLevel/5 + Luck/6 – ClassBonus – RaceBonus) * 5%

Race and class bonuses are always negative, meaning that in this formula, you are subtracting a negative, which is the same as adding a positive. I already listed the race bonuses, but these are the class bonuses:
  • Fighters get -3 on Save vs. Death
  • Mages get -3 on Save vs. Spell
  • Priests get -3 on Save vs. Petrify
  • Thieves get -3 on Save vs. Breath
  • Bishops get -2 on Save vs. Petrify, Wand, & Spell
  • Samurai get -2 on Save vs. Death & Spell
  • Lords get -2 on Save vs. Death & Petrify
  • Ninjas get -3 on Save vs. Death & Breath, -2 on Save vs. Petrify & Spell, and -4 on Save vs. Wand

MaxLev

This hidden stat remembers the highest level you’ve achieved without getting drained - e.g. you reached level 10 as a fighter and then changed your class to a mage and are now a level 1 mage, but you now have the HP of a level 10 fighter. To reflect this, your MaxLev=10.

If you get level drained, then your new maxHP gets set to:
OldMaxHP * NewCharacterLevel/OldMaxLev

And your MaxLev gets set to your new reduced character level.

So for instance, if you're drained from level 2 to 1, you would normally lose half of your maxHP. But if you were a level 10 fighter previously, then you’d lose 90% of your inflated maxHP, which is pretty harsh!

Level

Your chance to heal ASLEEP status per round is Level * 10%, but not more than 50%.

Your chance to heal AFRAID status per round is Level * 5%, but not more than 50%. As far as I know, no monsters in Wizardry I inflict this status.

Your chance to resist Manifo is (Level * 10%) + 50%.

Your chance to resist Badi is Level * 10%.

Your chance to resist Katino is Level * 20%.

On casting Haman or Mahaman, there is a chance of 1/Level of losing spells. In this event, each known spell has 50% chance of being unlearned.

Loktofeit has a success rate of Level * 2%.

If you open a trapped chest, or misidentify a trap type when disarming, the odds of not triggering the trap are Level * 0.1% (in other words, abysmal at any level).

A bishop's chance to identify an object is (Level * 5%) + 10%.

Successful or not, there is a 35% - (Level * 3%) chance of accidentally equipping a cursed item.

HitCalcMod

A hidden stat mentioned previously, used to determine your hit odds.

Base value for fighters, priests, samurai, lords, and ninjas is (CharacterLevel/3) + 2.

Base value for mages, thieves, and bishops is (CharacterLevel/5).

If your strength is higher than 15, you get a (Strength – 15) bonus. If it’s below 6, then you get a (6 – Strength) penalty.

This value is further increased by equipping weapons, each of which has an invisible value.

Each strike’s chance of hitting is:
(HitCalcMod + MonsterAC + (3*Victim) - 1) * 5%

This calculated value is then clamped to the 5% - 95% range. There is always at least a 5% chance of hitting and at least a 5% chance of missing, i.e. natural 1 and natural 20 in D&D parlance.

I am not 100% sure what “victim” means, but I think it refers to the monster’s group position in the fight. A group in the first position would have a victim value of 1, a group in the second position would have a victim value of 2, and so on, meaning monster groups in the rear group ranks are more vulnerable. This would be counterintuitive, and I have had trouble testing this theory, but it’s still my best guess on how it works.

ArmorClass

Base of 10, and reduced by armor and magic. All magic effects stack, except for multiple casts of Maporfic. Parrying has the invisible effect of reducing your AC by 2 for the round.

Your chance to be hit in melee per strike is:
(MonsterLevel + ArmorClass) * 5%

Just as with being on the delivering end of melee, the calculated value here is clamped to the 5% - 95% range. 

HealPts

Base of 0, and increased solely by inventory. Every step and combat round has a 25% chance of healing your HP by this amount. Doesn't stack in this version; the item in the character's inventory (equipped or not) with the biggest heal value is the one that takes. The Deadly Ring's negative value does not take, as the base of 0 is bigger.

CritHit

Boolean value, always true for ninjas, and conferrable to non-ninjas with some items. When true, you have a chance to inflict critical hits.

SwingCount

The number of strikes per combat round.

Fighters, samurai, and lords have values of:
(CharacterLevel/5) + 1

Ninjas get:
(CharacterLevel/5) + 2

10 is the limit for these classes, and every other class only gets 1.

Each strike has its own chance to hit or miss, as determined by HitCalcMod.

Some weapons have a SwingCount value of their own. This does not stack with your character's value; the higher value is the one that takes. For thieves, mages, priests, and bishops, this is the only way to get more than one strike per round.

HitDam

An invisible stat representing your damage dice. Base value is 2d2, and is overridden when equipping a weapon.

If your strength is higher than 15, you get a (Strength – 15) bonus. If it’s below 6, then you get a (6 – Strength) penalty.

So let’s say you are a level 1 ninja with 17 strength, and you have a 1d6+1 weapon. Your ninja class allows you two strikes per round at level 1, and your 17 strength confers a +2 HitDam bonus. When you attack, you hit up to twice, and each hit will do 1d6+3 damage.

LostXYL

A multi-purpose variable. This stores the coordinates in the dungeon, as well as your awards, which in Wizardry 1 only include the chevron. But during an active expedition, the ‘X’ value of your coordinates is instead used to store your poison value. Because of this quirk, disbanding your party cures poison.

When poisoned, every step and combat round has a 25% of reducing your HP by 1 (or reducing the effect of healing by 1). The engine allows for poison values greater than 1, but there are no game events that can raise your poison value past 1, and being poisoned while already poisoned does not make you more badly poisoned.

Wizardry

Dispell has a base success rate of 50% + (Level * 5%) – (MonsterLevel * 10%) per undead monster. Only undead with OK status may be dispelled.

Bishops learn Dispell at level 4, and have a -20% penalty.

Lords learn Dispell at level 9, and have a -40% penalty.

Priests always have Dispell and use it without penalty.

Anti-mage traps affect mages and samurai.

Anti-priest traps affect priests and bishops, but not lords.

New mages and bishops start with Halito and Katino. Characters who change their class to mage learn Katino.

New priests start with Dios and Badios. Characters who change their class to priest learn Dios.

All spellcasting classes have two spellpoint values, used for the purpose of determining how many SP’s are gained per circle per level. The bishop is treated as two separate classes here, one for its mage spells and one for its priest spells.

A B
Priest 0 2
Mage 0 2
Bishop (Priest) 3 4
Bishop (Mage) 0 4
Lord (Priest) 3 2
Samurai (Mage) 3 3

SP’s per circle per level are determined by this formula:
[Character Level] – ValueA + ValueB – (ValueB * Circle)

The value is clipped to the 0-9 range.

So, for instance, a level 9 bishop would receive spell points accordingly:

1 2 3 4 5 6 7
Mage 9 5 1 0 0 0 0
Priest 6 2 0 0 0 0 0

A character is guaranteed one spell point in each circle for each known spell. This number is not added to the previous value, but compared, and the bigger number takes.

For instance, suppose a priest reaches level 9. The formula says his priest SP’s should look like this:
9/7/5/3/1/0/0

But suppose he miraculously learns all six priest spells in the fifth circle, and already knows all of the spells in the first four circles. His guaranteed SP’s, then, would be:
5/4/4/4/6/0/0

The game compares the value for each circle and selects the bigger one, resulting in:
9/7/5/4/6/0/0

Through this mechanic, an accomplished spellcaster can change classes and retain a respectable SP pool, because spells are not forgotten when changing classes or losing levels.

Former spellcasters who changed class can actually learn spells when levelling up! All characters are eligible to learn spells in any circle where they know at least one spell. A Mage who reaches level 13 will automatically learn Malor. Even if he learned no other level 7 Mage spells, he could then change to a Fighter or Priest, and then be eligible to learn more level 7 Mage spells, simply because he already knows one. However, if a Mage changed class before learning Malor, he would only be eligible to keep learning level 6 Mage spells.

Thievery

A thief's chance to successfully inspect traps is Agility * 6%, but never more than 95%.

A ninjas' chance to successfully inspect traps is Agility * 4%, but never more than 95%.

Calfo works 95% of the time.

All other's chance is Agility * 1%.

A failed inspect or Calfo will reveal the name of a random trap type.

The chance for a thief or ninja to disarm a correctly identified trap is:
(50 + Character Level - Maze Level)/70

The chance for anyone else is:
(Character Level - Maze Level)/70

If disarming fails, the chance to avoid setting off the trap is Agility * 5%.

Ninjutsu

Whenever a ninja (or any character with CritHit) inflicts damage, the odds of delivering a critical hit are (CharacterLevel * 2%), but no more than 50%.

Multiple strikes do not grant multiple chances to inflict critical hits. The overall attack gives one chance for a critical hit, and only if it inflicted at least one damage point.


Despite what the manual says, you are better off equipping ninjas. Unarmed ninjas do 2d4 base damage. That's better than the 2d2 of other classes, but it’s not hard to find better weapons! Ninjas can score critical hits just as well and just as frequently with weapons as without.

As for armor, a naked ninja’s AC is:
8 – [Character Level]/3

Not really worth it, I think! A naked ninja would need to be level 21 to match the effect of wearing just Evil Plate +3.

Leveling up

When you level up, each stat has a 75% to change.

If a stat changes, there is an (AgeInYears/130) chance that it decreases by 1 point. Otherwise, it increases by 1 point.

If a stat would decrease from 18 to 17, there is a 5/6 chance that it will stay at 18 anyway.

If a stat would increase from 18 to 19, then it will stay at 18 instead.

Whenever you level up, your maxHP is essentially re-rolled.
  • Fighters and lords roll d10’s.
  • Priests and samurai roll d8’s.
  • Thieves, bishops, and ninjas roll d6’s.
  • Mages roll d4’s.

Each class rolls once per level, except for Samurai, who roll one additional time (e.g. a level 5 samurai rolls 6 times). Each roll is further modified by the character’s vitality.

So, for example, if a Thief reaches level 12, and has vitality of 18, this means rolling 1d6 +3 twelve times and summing the results. We would expect an average sum of 78. This sum becomes the thief’s new HP, provided it’s bigger than the previous value.

If the new HP value is not greater than it was before levelling up, then the result is discarded and you just gain 1 HP.

Because maxHP is recalculated on each level up, and it can’t go down, it trends toward the high end of what’s possible for your class. This is also why HP gains are slow after switching classes; your HP is higher than it “should” be for your new class at low levels, so your HP won’t grow until you’re a high enough level that you “should” be gaining again.

Aging

Surprisingly, and contradicting the manual, resting does NOT increase your age! Is this a bug?

Age is internally stored in weeks, but displayed in years.

New characters are a random age from 18 years, to 23 years and 39 weeks.

Changing class ages the character 1d3+3 years, plus 44 weeks.

Disbanding the party ages everyone in it 25 weeks.

Any service at the Temple of Cant ages the character 1d52 weeks.

Despite what the manual says, age does not affect the success rate of resurrection. Vitality does, though.

Statuses

Statuses from best to worst are:
OK, AFRAID, ASLEEP, PLYZE, STONED, DEAD, ASHES, LOST

It is not possible to have multiple statuses, and if you are inflicted with one while already inflicted with another, then the worse status will keep. Poison, however, is not a status, and can exist concurrently with one.

Prices to cure status at the Temple of Cant:
  • PLYZE: 100 * CharacterLevel
  • STONED: 200 * CharacterLevel
  • DEAD: 250 * CharacterLevel
  • ASHES: 500 * CharacterLevel
Poison is automatically cured when returning to town.

Alignment

The party alignment is determined by its first non-neutral member. If all members are neutral, then the party is neutral.

Neutral and evil parties will never encounter friendly monsters.

If you choose to fight a friendly group of monsters, each good party member has a 1/2000 chance to turn evil.

Misc

Every step has a 1% chance of an encounter.

Kicking a door into a treasure room which has no treasure chest has a 12.5% chance of causing an encounter.

When you run, odds of success are:
39% – (MazeLevel * 3%)

If the party size is 3 or less then add this to the above odds:
20% - (PartyCount * 5%)

If the monsters are demoralized (e.g. some of them want to run), then add 20% to the odds.

Running NEVER works in level 10!

In any unfriendly encounter, there is a 19% chance of surprising the monsters, and a 15.4% chance that the monsters surprise you.


That’s plenty for this post, but we’re far from done. More data is to come in the subsequent posts.

14 comments:

  1. I am enjoying your blog very much, and I am using your Wizardry analysis to play it myself.

    I have noticed a few things with your data. I am playing the Apple2 version, the one you "fixed" and has the Lv7 Fighters.

    1. The Monsters' abilities Poison and Paralyze seems to be swapped.

    2. My Fighter who has an AC of -4 receives a hit from rotting corpse and gets paralyzed. From my understanding, if the absolute value of AC is higher than the monster level, you should be able to evade the attack 100%, correct?? But it looks like it doesn't work that way.

    3. About saving throws. In your analysis, "wand" is meaningless and Elf has that bonus. At Zimlab, there is no "wand" and instead, breath is differentiated from gas trap and Elf has the bonus for breath.

    ReplyDelete
    Replies
    1. It seems like the NES version adds a constant P to the Monster level where P is related to the position of the monster and its poison status.

      Could it be that the correct formula for chance of milee hit by the monster involves the "victim" value that is used to calculate the chance to hit for your player characters?

      Delete
    2. Regarding the evasion formula for AC, it could be that the term (Monster LV + AC) is always a positive value. It looks like no matter how good the AC is, you get hit roughly around 5% of the time. (I experimented against [4 Grave Mists + 3 Rotting Corpses + 2 Shades]

      I apologize in advance if this was mentioned somewhere in your post and I overlooked.

      Delete
    3. Hey, thanks for all that feedback!

      On versioning, I would recommend playing with the WOZ copy that postdates my playthrough. The setup is a bit more involved - you'll need to use the utilities to make a playable copy (and last time I checked this works in MAME but not AppleWin, but you can play in AppleWin) - but there would be more confidence in the authenticity of the copy.

      On poison and paralysis, damn, you're right, and I'm not sure how this mix-up happened. I'll fix the bestiary.

      On AC and hitting, odds are always clamped to 5% to 95%, as if a natural 1 always misses and a natural 20 always hits. Goes both ways. I'll edit this post to mention that.

      On breath/gas traps, the code references the property LUCKSKIL[3] for both, and gives dwarves a -4 bonus to that. Elves get a -2 bonus to LUCKSKIL[2] but this property is never referenced anywhere else. Either Ewer's code is wrong or Zimlab is wrong. Could also be that this got changed in Wizardry 3, and Zimlab is basing the formula on that.

      I haven't played the NES version but I did look at the hex a bit in order to cross-reference values for monsters and Boltac's inventory. I'm not sure I understand what you mean here - monsters don't really have positions and you can't poison them. Do you mean characters? Because Apple II version has LostXYL which does this.

      Delete
    4. Hi Ahab. Thank you for updating your page with the corrections.

      As for the save throws against WAND, I just wanted to make sure because I made all my characters dwarves just to get that -4 bonus!! I trust your analysis.

      As for the hit probability calculation in the NES, the equation is quite complex, and it is also bugged. In the NES version, AC of your party characters does not affect the evasion probability. Instead, the equations looks up the poison status of your party characters/character ID in the roster/previous inspection on treasures/etc depending on which enemy you attacked. I found the analysis in Japanese.

      I wasn't aware of the 5-95% clamping, so that answer solved my question.

      I have an additional question if you don't mind . . .

      Is the 'flame' element and the 'fire' element different? Is 'flame' only for breath attacks? (In other words, does the Flame Rod have elemental resistance against fire spells?

      Delete
    5. Flame rod will reduce its holder's damage taken from group-targeting fire spells. Annoyingly, the Apple II version doesn't identify the spells that enemies cast on you. I'll update the Spellbook page to consistently call them flame spells.

      Delete
  2. In the first edition of D&D Save .vs. Wands was a save against being paralyzed or polymorphed. I don't know if it's not wired up as such, but that's where the terminology originated.

    ReplyDelete
  3. Do you know what the crit-chance would be for a Lord equipping the Lord's Garb?

    ReplyDelete
  4. Minor correction: "Bishops learn Dispell at level 3 ... Lords learn Dispell at level 8 ..." These should be levels 4 and 9, respectively. The test is >, not >=.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. [Reposted after correcting and clarifying the count of rounds and dispel attempts in the example.]

      I'm sorry to add another dispel-related remark so soon after you made that fix, but I realized something odd from reading the dispel code (DODISPEL) carefully. It checks that each monster in the targeted group has OK status before calculating whether the dispel "hits", so monsters which are not OK are totally unaffected by a dispel. We know that the only non-OK status which the game can inflict on monsters that haven't been killed is ASLEEP, and the only undead monster which can be "slept" with KATINO is Murphy's Ghost--but recall that when monsters are "held" with MANIFO, they too get ASLEEP status! MANIFO is just as effective on undead monsters as on other types. So suppose you cast MANIFO on a group of undead, and it hits some. Suppose further that they stay "held" (with ASLEEP status) into the next round. Now dispel the group. Even if you're so strong and the undead so weak that your dispel should dissolve them all, it will have no effect on the "held" undead!

      I tried this myself, with a party including a level 13 priest, on a group of 4 Undead Kobolds encountered on level 1. After repeated tries of MANIFO, I managed to get 2 of them "held". The next round, I dispelled, and the two undead not "held" were dissolved, but the two "held" undead were untouched. In fact, they stayed "held" into the third round, and a second dispel was totally ineffective on them. Finally, in the fourth round the "hold" wore off, and a third dispel eliminated them. Since the normal chance of a level 13 priest to dispel an undead kobold (2 hit dice) is 95%, I think this confirms that dispel has no effect on non-OK undead.

      Delete
    3. Also noted. I imagine the OK check is intended to prevent dispelling dead monsters.

      Delete

Most popular posts