DM mechanics - Azizi through the mirror of dawn
Posted: Tue Jul 14, 2020 2:30 am
A bit of a story to start..
DM has always been magical for me. I come back to it every 5 years or so to see what's new.
I looked at Legend or Grimrock, but it didn't feel the same, you know.. ..that feeling when you've driven the best and nothing else will just quite do. I am open to new ideas, but I have not found many games that have engaged me quite as DM did. I did spend a few years in X3, with one game save and such big fleets and factory complexes. Ive put quite a few hours into cities skylines, using my art and blender skills to create my own models and stuff and I've played world of tanks in my clan for many years, but the nostalgia for DM is strong.
I first posted here in 2004 I think, and when coming back this time, I was surprised that my lastpass wanted to autolog me - I'd forgotten I was here all those years ago. And before I start on the point of this thread, I want to give a huge shout out to Paul Stevens, Christophe F, George Gilbert and Sophia for the amazing work they have done in constructing and deconstructing DM so that we can muck about a bit. Equally huge shout to the extremely helpful forum regulars - you know who you are. Also kudos to Zolaerla for his vid
So, I find myself on holiday, but stuck at home mostly (I do go out with the kids from time to time, we have a crazy golf obsession) because covid cancelled my holiday
I'm a composer and former beta tester for FL studio, XOR I am known as (and XOR42 if you play world of tanks on the eu servers). You can find my stuff here and here if you are interested.
Anyhoo - I was remixing some stuff and my mind went back to the B.I.G Demo on the atari ST - so I went, got hatari, got a rom, found an image and managed to get the B.I.G Demo working on my PC.
Big mistake, it made me think of DM straight away. I started watching Gears of Games hes got a 46 part DM playthrough that he started mid last year. Oh dear, I had to have a go again..
Downloaded CSBwin
Downloaded ADGE
Downloaded CSBwin source
Downloaded ReDMCSB
Started browsing these forums again....
..and for some reason I stumbled across the comment on DM encyclopedia that anti-fire and anti-magic never worked on any Atari version of the game (Due to a compiler error according to Christophe)
I chatted to Paul about it and he showed me the comments in the code (seems it is fixed to work in CSBwin)
What else was DM hiding?
So I've being looking at my CSBwin trace logs and trying to follow it through in the source code. For some reason, my brain finds it fun to dig through this stuff and find out what the actual F is going on. So hopefully (as long as I keep finding it interesting) I'll keep going.
Part 1 - What the actual F is going on when you hit a monster with a weapon?
The trace log as quotes, bits of pseudo code, code, source code as code, my enlightened comments outside of these two.
I'm using CSBwin 17_6 and the latest source file package I could find.
Dungeon Master Encyclopedia is shortened to DME here after
For the different action stats on DME
Here is my character, GOFFER attacking a Trolin at the end of level 2
We start in..
//Function RESTARTABLE _Attack, line 1629, attack.cpp
D6 gets loaded with action_hit_probability (byte20002) of attackType 2 which in this case is 48 for chop. According to the DME this value is out of 75, so chop has a 48/75 % chance to hit, in this case 64%
D7 gets loaded with action_base_damage (byte19958) of attackType 2 which in this case is 48 for chop
The function looks at if the attack is valid or not (facing the right way, something infront of you etc...) and then calls the next function...
Checks wether a physical attack is possible, whether you are trying to attack with a dead character etc.
levelDifficulty = 1 is obvious, its the dungeon level experience multiplier DME
It also sets up a variable "objNI_attackWeapon" which is basically to know what weapon you are using
Checks whether the attack is a vorpal blade or disrupt attack and then calls the next function, quickness..
//function Quickness, line 2210, character.cpp
Well, Paul calls it quickness, but the above code works out your classic D&D "to hit roll" vs the monsters "needed to hit roll"
So to_hit = your current dexterity + rand(8), so for GOFFER this would be (52-60), I got 58
The next 2 lines work out your load penalty and subtract it from to_hit:
Loading effect=(to_hit/2)*(load=154)/(maxload=470)-->9 I think this is pretty obvious. GOFFER is carrying 15.4 kg / 47 kg.
Therefore if my load had been 0 I would have had a penalty of 0. A load of 47 kg, would have been 29 which means the penalty is 0-50% of your to_hit roll
So, my to_hit is 58 minus my weight penalty of 9 = 49
It then checks to see if you are sleeping and if true to_hit is halved (How do you attack whilst sleeping?)
Then your to_hit is flat divided by 2, so I get 24 (it's rounded down, obvs.)
Then two random limits are set up loLimit = rand(8)+1, hiLimit = 100-rand(8), so the limits are 1-9 to 92-100, if your to_hit falls outside the limits, it gets set to them
Quickness ends returning your to_hit chance, for this case 24
Important note: Notice that action_hit_probability is not used anywhere in calculating your to_hit chance. This seems odd to me. In fact the action_hit_probability is only used in your luck roll, if you miss normally.
//Back to DeterminePhysicalAttackDamage
So for a Trolin on level 2, it's needed_to_hit could range from 26 to 58
hyp: for a Trolin on level 13, it's needed_to_hit could range from 31 to 63 //dungeon level experience multiplier=6
hyp: for a Dragon on level 13, it's needed_to_hit could range from 60 to 92 //dungeon level experience multiplier=6, defence=70
hyp: for a Wasp on level 6, it's needed_to_hit could range from 137 to 169 //dungeon level experience multiplier=3, defence=150 (highest monster defence rating)
You can see above, my to_hit failed, but my option 2 lucky number succeeded, but lets look at option 3 for completeness.
//function IsCharacterLucky, line 2166, character.cpp
pchA3 is the character performing the attack
The important stat is the 75-P7, which the function calls "luckNeeded", with P7 being the value for action_hit_probability which is 48 for the chop action. So 75 - 48 = 27.
Important note: As stated above, action_hit_probability only seems to be used here in luck.
Bizzarely it looks like there are 2 checks in the code;
So this is (maybe one of) the mechansim(s) that increase your luck if you fail and reduce it if you succeed
//Back to DeterminePhysicalAttackDamage
So if all your to_hit chances fail, it deducts 2+rand(1) from your stamina and that's it, the attack ends with no damage.
Otherwise, it moves on to work out how much damage you do.. with a 2 phase calculation starting with a call to function DetermineThrowingDistance
Buckle yourselves in, this one is rough.....
I'll colour the numbers orange as I go down so you can see where they are coming from.
That line of numbers I got with the timer trace turned on as well as the attack trace. Deciphering them was a little trickier.
I've pointed out to Paul that this function name is misleading, it's actually working out the base damage done. If you are getting hung up on all the D numbers these are just registers hold variables, just follow the logic and it might make sense. Here's what that string of figures in the trace file are...
D3 is loaded with current character strength. I got 46 on GOFFER
D7 is loaded current character strength + rand(15). I got +1 so 47, D7 is the damage_coefficient it is working on
D6 is loaded with the weight of the weapon you are using. So 33 for the falchion (it's 3.3 KG) I'll call it weapon_weight
D5 is loaded with current characters max load / 16. So 47 for GOFFER divided by 16 = 2.93, hence the 29, this is load_coefficient
Next comes some very convoluted math to determine a weighted bonus
This is untrue in this case because the weapon_weight of the falchion is 33 which is greater than load_coefficient of 29 but it would have been = 47+33-12 = 68
This would be ((29-12)/2+29) = 37 (37.5 but rounded down)
So the load_coefficient (D5) which is 29 has been dropped for temp_coefficient_B which is 37, obviously, the 33 weight falchion is going to pass this test
It works out as (47+(33-29)/2) = 49 for GOFFER because it's true
This would be (47-2*(33-37) = 55 if it were true, but it is not
Now a small check...
This is 49+30 (my current damage_coefficient + 30, the base damage of the falchion) and this is where the 79 comes from in the string of figures.
If I had passed test 1 it would be 68+30= 98 or if it had passed test 3 it would have been 55+30 = 85
Important: it's weapon_base_damage (the falchion) not action_base_damage (chop)
Next it checks a byte on the weapon to work out which type of mastery it should use as a damage multiplier, for the falchion byte1 = 0 which is the next figure after the 79
If I am correct reading the code;
Odd to me that if byte= 2 it could pass both tests 1 and 2. Dagger, Axe, Hardcleave have a value of 2. //note to self, ask Paul
Anyhow, It calls the function DetermineMastery on the result which is 4 and my mastery_level in swing is 4, Apprentice
This is (79+2*4) = 87. If I had << master in swing, it would be ((79+2*10) = 99.
Next it does a funky little function call to StaminaAdjustedAttribute with my damage_coefficient of 87
//function StaminaAdjustedAttribute, line 4291, Code11f52.cpp
Paul makes this one easy with his comments "// If below half stamina"
Basically, if you are below half stamina it divides damage_coefficient by 2 and adds a portion back based on your current stamina vs maximum stamina. Otherwise it does nothing and returns the original value it was given.
In my case , it does not affect me, so the next figure in the string of numbers stays at 87
//Back to DetermineThrowingDistance
Next, this function works out if you are hurt in the hand. I am not, so the next figure in the string of numbers is 0, not hurt.
If your hand is hurt, damage_coefficient is halved.
damage_coefficient is arbitrarily halved given the final figure in the string, 43 (43.5 rounded down)
Phew, we finished the DetermineThrowingDistance which is actually base damage.
then it's multiplying it by action_base_damage/32 which is (48/32) = 1.5 so 50*1.5 = 75
Important: this is the action_base_damage, so chop which is 48, not the weapon_damage.
Now we have our final temporary damage, time to work out how much the monsters AC mitigates it...
A few more examples;
for a rockmonster this would be (0 to 32)+170+1 = AC_coefficient of 171-203
for a screamer this would be (0 to 32)+5+1 = AC_coefficient of 6-38
Now you can see why you get such small damage against rockmonsters, it's easy enough to win the to_hit roll, but nearly all the damage you do gets mitigated.
Now that variable objNI_attackWeapon comes into play..
If you are using the Diamond edge, 25% of the monsters AC_coefficient is lopped off
If you are using the Executioner, 12.5% of the monsters AC_coefficient is lopped off
D7 (temp version of damage_coefficient) + rand(32) - monster AC_coefficient, so this could have been 75 + (0 to 32) - 49. So D7 (temp version of damage_coefficient) = 26 to 58
It places the value of D7 (temp version of damage_coefficient) into D6 so it can do the following check:
basically you hit but are about to do 0 damage
In which case the character gets another 3 in 4 random chance to inflict damage.
Luck out, reduce stamina and return a 0 damage
It adds a random(16) to your D6 (the copy of temp version of damage_coefficient)
Yup! even if your rand in D6 was a 0, you still get another 1 in 2 chance for this to be true (the random boolean). The game really wants you to do some token damage
Yet another 1 in 4 chance to do damage
The rand(16) is limited to a max of D6
Finally out of all that random shenanigans!
Now that we have passed the damage mitigation part and got into the "so how much damage am I going to do" part, we are going to run through, what looks like to me, a set of normalisation algorithms
So D7 (temp version of damage_coefficient) is halved, D0 becomes a rand of D7, so rand(20) in this case, and D1 (not seen in the trace) is a rand (3).
There is also a similar section of code, just to workout vorpal blade damage which I won't go into here.
But it's not entirely finished. The next bit of code is not in my log, but if it does happen it will output to the log
//function DetermineMastery, line 13, charactercode17818
You can see in the log the function of this and result, Mastery of skill 6 is 3 or I'm a novice at club
//Back to DeterminePhysicalAttackDamage
So D7 is the final damage you do to the monster, but if on a rand(64) you roll less than you skill_ mastery_level(skillNumber) which you recall is 3, the level of my (club) skill then it adds 10 damage.
So as a neophyte in club, you have a 1 in 32 chance of adding 10 damage
So as a << master in club, you have a 1 in 6 chance of adding 10 damage
I believe this to be a "critical hit" mechanic
Then this happens in the c++ code....
...which actually damages the monster.
D0 = (D7) * ((Bits8_11(i26.word16)=1)/16) +3 so; According to ADGE, bits 8-11 of word 16 are "Skill" DME calls it "Experience class"
My Trolin is skill=1 which means the above is (8*0.0625)+3 so; 3.5 rounded down to 3
A few examples plugged into the above formula
Giant scorpion=9 (8*(9/16))+3 = 7
Materializer=12 (8*(12/16))+3 = 9
So adjust skill 6 by 3xp , meh.
Decrement stamina by 4 + rand(3) (I know the log says 4, but the c++ says STRandom0_3 which I take as 0-3, so 4 values possible)
Again, another section of code after this describes the process for a vorpal weapon. Not going into that here.
Just returning values, nothing to see here.
This line is showing the 10 xp for chop stored in experiencedGained being added to skill 6 and skill 0 (0a being 10 in hex)
End of attack trace and end of part 1.
2.06 am tired, going to bed.
Nick K
DM has always been magical for me. I come back to it every 5 years or so to see what's new.
I looked at Legend or Grimrock, but it didn't feel the same, you know.. ..that feeling when you've driven the best and nothing else will just quite do. I am open to new ideas, but I have not found many games that have engaged me quite as DM did. I did spend a few years in X3, with one game save and such big fleets and factory complexes. Ive put quite a few hours into cities skylines, using my art and blender skills to create my own models and stuff and I've played world of tanks in my clan for many years, but the nostalgia for DM is strong.
I first posted here in 2004 I think, and when coming back this time, I was surprised that my lastpass wanted to autolog me - I'd forgotten I was here all those years ago. And before I start on the point of this thread, I want to give a huge shout out to Paul Stevens, Christophe F, George Gilbert and Sophia for the amazing work they have done in constructing and deconstructing DM so that we can muck about a bit. Equally huge shout to the extremely helpful forum regulars - you know who you are. Also kudos to Zolaerla for his vid
So, I find myself on holiday, but stuck at home mostly (I do go out with the kids from time to time, we have a crazy golf obsession) because covid cancelled my holiday

I'm a composer and former beta tester for FL studio, XOR I am known as (and XOR42 if you play world of tanks on the eu servers). You can find my stuff here and here if you are interested.
Anyhoo - I was remixing some stuff and my mind went back to the B.I.G Demo on the atari ST - so I went, got hatari, got a rom, found an image and managed to get the B.I.G Demo working on my PC.
Big mistake, it made me think of DM straight away. I started watching Gears of Games hes got a 46 part DM playthrough that he started mid last year. Oh dear, I had to have a go again..
Downloaded CSBwin
Downloaded ADGE
Downloaded CSBwin source
Downloaded ReDMCSB
Started browsing these forums again....
..and for some reason I stumbled across the comment on DM encyclopedia that anti-fire and anti-magic never worked on any Atari version of the game (Due to a compiler error according to Christophe)
I chatted to Paul about it and he showed me the comments in the code (seems it is fixed to work in CSBwin)
What else was DM hiding?
So I've being looking at my CSBwin trace logs and trying to follow it through in the source code. For some reason, my brain finds it fun to dig through this stuff and find out what the actual F is going on. So hopefully (as long as I keep finding it interesting) I'll keep going.
Part 1 - What the actual F is going on when you hit a monster with a weapon?
The trace log as quotes, bits of pseudo code, code, source code as code, my enlightened comments outside of these two.
I'm using CSBwin 17_6 and the latest source file package I could find.
Dungeon Master Encyclopedia is shortened to DME here after
For the different action stats on DME
Here is my character, GOFFER attacking a Trolin at the end of level 2
We start in..
//Function RESTARTABLE _Attack, line 1629, attack.cpp
Attacking from location x to location yGOFFER attacks from 02(1d,19) to 02(1d,1a)
He's using chop on the falchionAttack type = 2 = CHOP
Hidden skill 6 (club) selectedSkill number required = 06
Stamina cost of using chop = 8Byte20046[attackType=2] = 8
staminacost is = action_stamina_cost + (0-1), so 8-9staminaCost = Byte20046[attackType=2] + Random(1) = 9
experiencegained = actions_experience_gain stat, so 10 for chopexperiencedGained = Byte20178[attackType=2] = 10
//function AttackWithPhysicalForce, line 1048, attack.cppAttack BASH/HACK/BERZERK/KICK/SWING/CHOP
Enter AttackWithPhysicalForce (attackType=2, skillNumber=6)
D6W=Byte20002[attackType=2]=48
D7W=Byte19958[attackType=2]=48
D6 gets loaded with action_hit_probability (byte20002) of attackType 2 which in this case is 48 for chop. According to the DME this value is out of 75, so chop has a 48/75 % chance to hit, in this case 64%
D7 gets loaded with action_base_damage (byte19958) of attackType 2 which in this case is 48 for chop
The function looks at if the attack is valid or not (facing the right way, something infront of you etc...) and then calls the next function...
//function DeterminePhysicalAttackDamage, line 532, attack.cppCalling DeterminePhysicalAttackDamage(character, monster, D5W-1, D6W, D7W, skillNumber)
Entering DeterminePhysicalAttackDamage(char=1,monster,P4=0,P7=48,P8=48,skillNumber=6)
levelDifficulty = 1
pi26 word2 &0x40 == 0 or vorpalOrDisrupt
Checks wether a physical attack is possible, whether you are trying to attack with a dead character etc.
levelDifficulty = 1 is obvious, its the dungeon level experience multiplier DME
It also sets up a variable "objNI_attackWeapon" which is basically to know what weapon you are using
Checks whether the attack is a vorpal blade or disrupt attack and then calls the next function, quickness..
Entering function to determine character's Quickness
quickness = (Dexterity=52) + (random(8)=6) --> 58
About to compute Maximum load
Loading effect=(quickness/2)*(load=154)/(maxload=470)-->9
Subtract loading effect from quickness -->49
Low Limit = random(8) + 1 --> 8
High Limit = 100-random(8) --> 93
Final quickness = 24
character's quickenss = 24
//function Quickness, line 2210, character.cpp
Well, Paul calls it quickness, but the above code works out your classic D&D "to hit roll" vs the monsters "needed to hit roll"
So to_hit = your current dexterity + rand(8), so for GOFFER this would be (52-60), I got 58
The next 2 lines work out your load penalty and subtract it from to_hit:
Loading effect=(to_hit/2)*(load=154)/(maxload=470)-->9 I think this is pretty obvious. GOFFER is carrying 15.4 kg / 47 kg.
Therefore if my load had been 0 I would have had a penalty of 0. A load of 47 kg, would have been 29 which means the penalty is 0-50% of your to_hit roll
So, my to_hit is 58 minus my weight penalty of 9 = 49
It then checks to see if you are sleeping and if true to_hit is halved (How do you attack whilst sleeping?)
Then your to_hit is flat divided by 2, so I get 24 (it's rounded down, obvs.)
Then two random limits are set up loLimit = rand(8)+1, hiLimit = 100-rand(8), so the limits are 1-9 to 92-100, if your to_hit falls outside the limits, it gets set to them
Quickness ends returning your to_hit chance, for this case 24
Important note: Notice that action_hit_probability is not used anywhere in calculating your to_hit chance. This seems odd to me. In fact the action_hit_probability is only used in your luck roll, if you miss normally.
//Back to DeterminePhysicalAttackDamage
Required quickness is the needed_to_hit. It's calculated in D1. It combines pi26.uByte8[4] which is the monsters defence rating (41) for the Trolin I am attacking with a rand (32), the dungeon level experience multiplier (1 for level 2) and then a - 16. I get 26.required quickness = (pi26.uByte8[4]=41) + (random(32)=0) + (levelDifficulty=1) - 16 = 26
So for a Trolin on level 2, it's needed_to_hit could range from 26 to 58
hyp: for a Trolin on level 13, it's needed_to_hit could range from 31 to 63 //dungeon level experience multiplier=6
hyp: for a Dragon on level 13, it's needed_to_hit could range from 60 to 92 //dungeon level experience multiplier=6, defence=70
hyp: for a Wasp on level 6, it's needed_to_hit could range from 137 to 169 //dungeon level experience multiplier=3, defence=150 (highest monster defence rating)
Next come 3 checks to see if "you hit"Attack was successful because Lucky Hit (1 chance in 4)
Code: Select all
1. if your to_hit is > needed_to_hit, you hit. If not, next option
2. a rand(3) is generated, if it's = 0, you hit, if not, next option
3. Call IsCharacterLucky (pchA3, 75 - P7, nextTracePrefix(traceID))
//function IsCharacterLucky, line 2166, character.cpp
pchA3 is the character performing the attack
The important stat is the 75-P7, which the function calls "luckNeeded", with P7 being the value for action_hit_probability which is 48 for the chop action. So 75 - 48 = 27.
Important note: As stated above, action_hit_probability only seems to be used here in luck.
Bizzarely it looks like there are 2 checks in the code;
Code: Select all
1. if a rand(100) > luckNeeded return true - i.e. you hit!
2. if a rand(yourcurrentluck) > luckNeeded , deduct 2 from ourcurrentluck and return true (you hit!) else add 2 to yourcurrentluck and return false
//Back to DeterminePhysicalAttackDamage
So if all your to_hit chances fail, it deducts 2+rand(1) from your stamina and that's it, the attack ends with no damage.
Otherwise, it moves on to work out how much damage you do.. with a 2 phase calculation starting with a call to function DetermineThrowingDistance
Buckle yourselves in, this one is rough.....

//function DetermineThrowingDistance, line 2268, character.cpp46 47 33 29 79 0 4 87 0 43
ThrowingDistance char=1 hand=1 value=43
I'll colour the numbers orange as I go down so you can see where they are coming from.
That line of numbers I got with the timer trace turned on as well as the attack trace. Deciphering them was a little trickier.
I've pointed out to Paul that this function name is misleading, it's actually working out the base damage done. If you are getting hung up on all the D numbers these are just registers hold variables, just follow the logic and it might make sense. Here's what that string of figures in the trace file are...
D3 is loaded with current character strength. I got 46 on GOFFER
D7 is loaded current character strength + rand(15). I got +1 so 47, D7 is the damage_coefficient it is working on
D6 is loaded with the weight of the weapon you are using. So 33 for the falchion (it's 3.3 KG) I'll call it weapon_weight
D5 is loaded with current characters max load / 16. So 47 for GOFFER divided by 16 = 2.93, hence the 29, this is load_coefficient
Next comes some very convoluted math to determine a weighted bonus
Code: Select all
if weapon_weight (D6) <= load_coefficient (D5) then damage_coefficient (D7) = damage_coefficient (D7) + weapon_weight- 12
Code: Select all
else
temp_coefficient_A = ((load_coefficient-12)/2+load_coefficient)
Code: Select all
temp_coefficient_B = temp_coefficient_A
if weapon_weight <= temp_coefficient_B then damage_coefficient = (damage_coefficient + (weapon_weight -load_coefficient)/2)
It works out as (47+(33-29)/2) = 49 for GOFFER because it's true
Code: Select all
else damage_coefficient = (damage_coefficient - 2 * (weapon_weight - temp_coefficient_A))
Now a small check...
Code: Select all
if held obj !=null and is a weapon then damage_coefficient = (damage_coefficient + weapon_base_damage)
If I had passed test 1 it would be 68+30= 98 or if it had passed test 3 it would have been 55+30 = 85
Important: it's weapon_base_damage (the falchion) not action_base_damage (chop)
Next it checks a byte on the weapon to work out which type of mastery it should use as a damage multiplier, for the falchion byte1 = 0 which is the next figure after the 79
If I am correct reading the code;
Code: Select all
1. if byte1 = 0 or 2, then get mastery of skill 4 (swing)
2. if byte1 != 0 and < 16 then get mastery of skill10 (Throw)
3. if byte1 >= 16 and < 112 then get mastery of skill 11(Shoot)
Anyhow, It calls the function DetermineMastery on the result which is 4 and my mastery_level in swing is 4, Apprentice
Code: Select all
damage_coefficient=(damage_coefficient + 2 * mastery_level)
Next it does a funky little function call to StaminaAdjustedAttribute with my damage_coefficient of 87
//function StaminaAdjustedAttribute, line 4291, Code11f52.cpp
Paul makes this one easy with his comments "// If below half stamina"
Basically, if you are below half stamina it divides damage_coefficient by 2 and adds a portion back based on your current stamina vs maximum stamina. Otherwise it does nothing and returns the original value it was given.
In my case , it does not affect me, so the next figure in the string of numbers stays at 87
//Back to DetermineThrowingDistance
Next, this function works out if you are hurt in the hand. I am not, so the next figure in the string of numbers is 0, not hurt.
Code: Select all
if hurt = true and hand = true then damage_coefficient=damage_coefficient / 2
Code: Select all
damage_coefficient=damage_coefficient / 2
Phew, we finished the DetermineThrowingDistance which is actually base damage.
Now we are modifying D7 (a new temp version of damage_coefficient) again first by a random of itself/2+1 so this would be 43 + (0 to 22) + 1 which gives a range of 44-66D7W = Throwing distance=43
D7W = D7W+random(D7W/2+1) = 50
D7W = D7W*(P8=48)/32 = 75
then it's multiplying it by action_base_damage/32 which is (48/32) = 1.5 so 50*1.5 = 75
Important: this is the action_base_damage, so chop which is 48, not the weapon_damage.
Now we have our final temporary damage, time to work out how much the monsters AC mitigates it...
So D4 is the monsters AC_coefficient, formed from it's static AC (ubtye8), 28 for a Trolin, +1 for dungeon level difficulty + rand(32). So I get 49 (well the monster does). See here for monster stats. This Trolin could have had an AC_coefficient of 29-61D4W = (i26->uByte8[0]=28) + (levelDifficulty=1) + (random(32)=20) = 49
A few more examples;
for a rockmonster this would be (0 to 32)+170+1 = AC_coefficient of 171-203
for a screamer this would be (0 to 32)+5+1 = AC_coefficient of 6-38
Now you can see why you get such small damage against rockmonsters, it's easy enough to win the to_hit roll, but nearly all the damage you do gets mitigated.
Now that variable objNI_attackWeapon comes into play..
Code: Select all
if (objNI_attackWeapon == objNI_DiamondEdge) then AC_coefficient=AC_coefficient-(AC_coefficient/4)
Code: Select all
if (objNI_attackWeapon == objNI_Executioner) then AC_coefficient=AC_coefficient-(AC_coefficient/8)
The big line, this works out how much temp damage_coefficient gets through or notD7W = D7W + (random(32)=15) - (D4W=49) = 41
D7 (temp version of damage_coefficient) + rand(32) - monster AC_coefficient, so this could have been 75 + (0 to 32) - 49. So D7 (temp version of damage_coefficient) = 26 to 58
What happens next is mad - lots of chances to do damage basically...D6W = D7W = 41
It places the value of D7 (temp version of damage_coefficient) into D6 so it can do the following check:
Code: Select all
if damage_coefficient = 0 or D6 <=1 then
Code: Select all
D7 = rand(3)
In which case the character gets another 3 in 4 random chance to inflict damage.
Code: Select all
if D7 = 0
character_stamina=character_stamina - 2+rand(1) // (2 to 3)
return 0
Luck out, reduce stamina and return a 0 damage
Code: Select all
if D7 != 0
D1 = rand(16) // (0-16)
D6 = D6 + D1
It adds a random(16) to your D6 (the copy of temp version of damage_coefficient)
Code: Select all
if D6 > 0 or rand(boolean) = true then
Yup! even if your rand in D6 was a 0, you still get another 1 in 2 chance for this to be true (the random boolean). The game really wants you to do some token damage
Code: Select all
D7 = D7 + rand(3)
if rand(3) = 0
Yet another 1 in 4 chance to do damage
Code: Select all
D7 = D7 + rand(16) + D6
The rand(16) is limited to a max of D6
Finally out of all that random shenanigans!
Now that we have passed the damage mitigation part and got into the "so how much damage am I going to do" part, we are going to run through, what looks like to me, a set of normalisation algorithms
Divide D7W by 2 --> 20
D0W=random(D7W) --> 3
So D7 (temp version of damage_coefficient) is halved, D0 becomes a rand of D7, so rand(20) in this case, and D1 (not seen in the trace) is a rand (3).
Now added together. This had the range of 20 + rand (20) + rand(3) so 20-43D7W += (random(4)=3) + (D0W=3) --> 26
Then if D7 != 0 add another rand of (D7) which is now 26 so the result could have been 26 + rand(26) so 26-52D7W += (random(D7W)=2)--> 28
Now D7 / 4 so; 28 / 4 = 7Divide D7W by 4 --> 7
AND FINALLY damage = D7 + rand (3) + 1 which is 8, all that math to arrive at 8 hps of damageD7W += (random(4)=0) + 1 --> 8
There is also a similar section of code, just to workout vorpal blade damage which I won't go into here.
But it's not entirely finished. The next bit of code is not in my log, but if it does happen it will output to the log
Code: Select all
D0 = rand(64)
Enter DetermineMastery(hero=GOFFER, skill=6)
Extract two flags from skill number.
flag8000 = skill&0x8000 --> 0
flag4000 = skill&0x4000 --> 0
SkillNumber = 6
Mastery = log2(Experience=469) --> 3
Mastery of skill 6 is 3
Code: Select all
D1 = DetermineMastery(pParam->charIdx, pParam->skillNumber, traceID==NULL?NULL:traceID-2))
//Back to DeterminePhysicalAttackDamage
Code: Select all
if D0 < D1 then D7=D7+10
So as a neophyte in club, you have a 1 in 32 chance of adding 10 damage
So as a << master in club, you have a 1 in 6 chance of adding 10 damage
I believe this to be a "critical hit" mechanic
Then this happens in the c++ code....
Code: Select all
pParam->attdep.physicalAttack.monsterDamage = D7W;
Working out some experience here for the damage doneD0W = ((D7W=8) * (Bits8_11(i26.word16)=1)/16) + 3 --> 3
D0 = (D7) * ((Bits8_11(i26.word16)=1)/16) +3 so; According to ADGE, bits 8-11 of word 16 are "Skill" DME calls it "Experience class"
My Trolin is skill=1 which means the above is (8*0.0625)+3 so; 3.5 rounded down to 3

A few examples plugged into the above formula
Giant scorpion=9 (8*(9/16))+3 = 7
Materializer=12 (8*(12/16))+3 = 9
AdjustSkills((skill=6), (D0W=3))
Decrement Stamina by random(4)+4 = 7
So adjust skill 6 by 3xp , meh.
Decrement stamina by 4 + rand(3) (I know the log says 4, but the c++ says STRandom0_3 which I take as 0-3, so 4 values possible)
Again, another section of code after this describes the process for a vorpal weapon. Not going into that here.
Skill 6 up by 3 xp, skill 0 up by 3 xpAdjustSkills chidx=01 skill=06 by 03 skill[06]=000002cd skill[00]=00000bd7
Monster[0] at 02(1d,1a) {HP=48} loses 08 hitpoints
w_2 = DamageMonster() --> 8
PhysicalAttack returning D7W = 8
Word20264 = Result of calling DeterminePhysicalAttackDamage = 8
Return 1
Just returning values, nothing to see here.
Just something in the log.000144a0 timer create 003 02 DLY=0008 0b 01 00 cd cd cd c783 de224393
Although not in the log, just before this code stamina gets decremented by our staminacost of (9)AdjustSkills chidx=01 skill=06 by 0a skill[06]=000002e1 skill[00]=00000beb
This line is showing the 10 xp for chop stored in experiencedGained being added to skill 6 and skill 0 (0a being 10 in hex)
Yup.Increase skill 06 by 0a to 2e1
End of attack trace and end of part 1.
2.06 am tired, going to bed.
Nick K