Decoding Blocks, or, Number Crunching Mayhem

https://attnam.com/topics/Decoding-Blocks-or-Number-Crunching-Mayhem

The Cathedral of Attnam > Tips, Tricks, and Secrets

#1 Oct 15, 2011, 4:21 am Hide

Ischaldirh

I was wondering with Chao about blocking capabilities of various things, and he mentioned Guugzamesh being a blocking fiend with his two short swords. So I dug up some stuff from Greatboards and the wiki and set to work trying to crunch numbers and find out some relevant and understandable information.

I screwed up somewhere along the line.

I've found that the IVANdevs were fond of creating extremely large numbers and then shaving off a lot of 0's (there's more than one instance of "divide by ten million" and the like). So I was very surprised when I started running into extremely small and not-neat decimal numbers (like, for example, 0.46616526154899332860822020122614... that's what I came up with as a final To-Hit Value for each of Guugzamesh's swords). I decided I must have been doing something wrong. I believe it had to do with weight values; I was using the in-game displayed weights, but I strongly doubt that the game mechanics themselves use those numbers. They're probably much, much larger. (item size * material density? I have a thread about this somewhere...)

So I thought I'd make a thread about it, and allow someone else to take a crack at it. The following pages will be useful:

Squashmonster's parsing of combat-related code
Guugzamesh's stats -- Keep in mind, as far as I understand, displayed stats are rounded averages -- so if it displays 20 AStr, that doesn't mean two 10 AStr arms, but two 20 AStr arms. This is consistent with every method I can find to check it.

You'll also need the materials list. Also maybe fire up the game... And for reference, when Squash refers to "Weight" he is in fact referring to ingame displayed weight. Checked via reverse engineering with his AStr requirement formula; ran it both ways (solved for both the AStr with known weight, and for weight with known AStr) and came out correct both times.

I suspect Squash made some mistakes, however. A code dive may be in order...
#2 Oct 15, 2011, 4:30 am Hide

4zb4

There are some things man was never meant to know...
The very act of trying to piece together the numbers may well drive you insane!

/on topic:
If I can harvest some spare time, I might look into this.
#3 Oct 15, 2011, 4:32 am Hide

Ischaldirh

This is also the contents of the txt document I was working out of. Might save you some time...

Weapon weight = 500    /* mithril short sword */

1 astr requirement    /* actually a small decimal... game seems to round up however */

Hit strength = 19

1400 class skill bonus
1450 specific skill bonus

weapon THVBonus = 1    /* they're +2 */

move ease = 100    /* I assume he's unburdened */

/* This is where things got weird... */

to-hit-value
2 * 10^(-11) * 19 * 1400 * 1450 * 100 * 10000 / (stuff)
77140000000000 * 10^(-11) / (stuff)
771.4 / (stuff)
Stuff: 1000 + 500 + 1
771.4 / 1501
Base = 0.54097268487674883411059293804131
ThisToHit = dex * sqrt(2.5*per)
20 * sqrt(2.5*30)
ThisToHit = 173.20508075688772935274463415058
1 + (500 * 500) / (1000 + 500 / 2)
1 + 250000 / 250000           /* Actually my second pass at this. For this try I made an assumption about squash's work, assuming a + in the previous line was actually a *... */)
2
ThisToHit = 86.60254037844386467637231707525
To-Hit-Value = 43.301270189221932338186158537625

Block value = 9.0932667397366057910190932929013

Block Capability = 19 * 90 * 1400 * 1450 / 10000000
            =347.13

Block Strength = 347  /* If this were true, the next part would reveal that you would need to deal some 350 damage in order to pass a successful block. I don't feel this is accurate. */
Block Value = 9     /* Also was unable to do anything further with this number as the next lines involve an attacker, and I was too tired to work one up. */

It may actually be easier to crunch something else; say, a fresh character with some default shield. Either way I'm going to bed, fuck these numbers...
#4 Oct 16, 2011, 10:25 am Hide

fejoa

Ischaldirh wrote
Block Capability = 19 * 90 * 1400 * 1450 / 10000000
=347.13

Block Strength = 347 /* If this were true, the next part would reveal that you would need to deal some 350 damage in order to pass a successful block. I don't feel this is accurate. */
[/code]

I'm looking at this presently:

I calculated the StrengthRequirement = 0.153.
Therefore, HitStrength is indeed 19, as Izzy has calculated.

in Bodypart.cpp there is this function:

int arm::GetBlockCapability() const

This function returns the value of the block capability of a humanoid's arm as follows:
Min(HitStrength, 10) * Wielded->GetStrengthValue() * GetHumanoidMaster()->GetCWeaponSkill(Wielded->GetWeaponCategory())->GetBonus() * (*GetCurrentSWeaponSkill())->GetBonus() / 10000000;

The first part is a Min function, which takes the minimum between two values f.ex. HitStrength and 10. So the result of Min(19, 10) = 10.

Next I calculate the strength value of the Short Sword to be (90 * 200)/2000 = 9

I do not know how to calculate class skill bonus or specific skill bonus so will assume they are as above, i.e. 1400 and 1450 respectively.

Then I have:
Block Capability = 10 * 9 * 1400 * 1450 / 10000000
                                  = 18.27

That is all I have for the moment, hope this helps



#5 Oct 17, 2011, 1:37 am Hide

Ischaldirh

Thanks warheck! This raises a couple questions...

Warheck wrote
Next I calculate the strength value of the Short Sword to be (90 * 200)/2000 = 9

How did you calculate this? I see Short Sword's StrengthModifier value (90) and Mithril's Strength value (200), but how did you find the formula (and the "/ 2000" part)?

Squashmonster wrote
If you have no weapon or our weapon's hit strength is 0 or less, then 0. If you're still here, pretend your weapon's hit strength was at least 10.

Having not seen the actual code myself, I did not realize this was a Min() function. Thus, I assumed it was either 0, or Max(HitStrength, 10).

Also, to answer your question, the class skill and specific skill bonuses are calculated (according to Squash) via: 1000 + (50 * SkillLevel). Guss has skill 8/9 with each sword.

Thanks, Warheck. I'm going to take this information and bang my head against the problem a bit more...
#6 Oct 17, 2011, 2:44 am Hide

Ischaldirh

So I tinkered around some more. The next part of the equation, according to Squash, is

if(RAND() % int(100 + WeaponToHitValue / Block Value / (1 << BlocksSinceLastTurn) * (100 + Success)) < 100)
then (block)

Which means that ideally for the defender, BlockValue is drastically larger than WeaponToHitValue, there have been no blocks since his last turn, and Success is 0. This will lead to, again ideally, a random number between 0 and (at least) 100; the closer the max number is to 100, the more likely the defender blocks.

I wasn't sure what WeaponToHitValue was so I just assumed it was the THV for the attacker. For the sake of not doing more math (and at Chao's suggestion) I assumed Guss was being attacked by Guss, which leads to a 50% chance for a successful block with each arm (so two 50% chances = 75% block chance).

HOOoooooly shit, now this is bizzare: I just got a bit of help from my coding friend and discovered something. I was unsure about " (1 << BlocksSinceLastTurn) " and how to handle it. Turns out "<<" is a bitshift operator in C. (If you don't understand that, look it up, it's complicated) Long story short, the first time you block, this value is 1. The second time, it becomes 2. The third time it's 4, then 8, 16, 32, and so on. I assume the intention is that it becomes exponentially more difficult to block multiple attacks in a turn. However, the surrounding code (as parsed by Squash, anyways) would imply that this number actually INCREASES the chance to block as it grows.

Double crap, I just realized I used the wrong number in WeaponToHitValue (I used Guss's block value instead of his THV). With his actual THV it becomes a 17% chance to block (but he still gets two chances at it). This seems... wrong.

I hate to ask you guys to go code-diving for me again... but can somebody dig up the code responsible for this? And ideally for determining some of these variables? I should be in bed already truth be told, and I certainly won't have time tomorrow...

EDIT: Also thought I'd mention, IVANcode is absolutely riddled with those bitshift operators. I remember when I was compiling the code in college, C++ compilers couldn't handle the C code because C++ uses a different operator for bitshifts. I didn't know what they were back then, I just remember seeing a royal goddamn shit-fuck-ton of the things.
#7 Oct 17, 2011, 3:51 pm Hide

fejoa

Ischaldirh wrote
How did you calculate this? I see Short Sword's StrengthModifier value (90) and Mithril's Strength value (200), but how did you find the formula (and the "/ 2000" part)?

It's in item.cpp, and the function goes a little something like this:

int item::GetStrengthValue() const
{
  return long(GetStrengthModifier()) * GetMainMaterial()->GetStrengthValue() / 2000;
}

Regarding the bitchift operator, you are correct. If k = BlockSinceLastTurn and (1 << k) signifies a 'left-shift' operation, then this is the same as multiplying the first operand, i.e. 1, by 2^k. F. ex. 1*2^k: 1*2^2 = 4.
Really it's like this: 0001 << 2 = 0100 = 4, (in binary of course). All the bits are shifted two places to the left.

I agree with all your observations of the code you mentioned here:
if(RAND() % int(100 + WeaponToHitValue / BlockValue / (1 << BlocksSinceLastTurn) * (100 + Success)) < 100)
then (block)

Ischaldirh wrote
Double crap, I just realized I used the wrong number in WeaponToHitValue (I used Guss's block value instead of his THV). With his actual THV it becomes a 17% chance to block (but he still gets two chances at it). This seems... wrong.

Isn't the THV just the ToHitValue of the weapon/item?
With a little sourcediving I think this may just be the case. The THV flows through several functions before finding its use in the blocking function, but I think it originates inside bodypart.cpp. Specifically, the game checks to see if the character is wielding something, and then calculates a THV (of the item/arm) based on what is being wielded (or not).

Therefore, in bodypart.cpp we have this extremely interesting piece of code:
double arm::GetWieldedToHitValue() const
{
  int HitStrength = GetWieldedHitStrength();

  if(HitStrength <= 0)
    return 0;

  citem* Wielded = GetWielded();

  double Base = 2e-11
		* Min(HitStrength, 10)
		* GetHumanoidMaster()->GetCWeaponSkill(Wielded->GetWeaponCategory())->GetBonus()
		* GetCurrentSWeaponSkillBonus()
		* Master->GetMoveEase()
		* (10000. / (1000 + Wielded->GetWeight()) + Wielded->GetTHVBonus());
  double ThisToHit = GetAttribute(DEXTERITY) * sqrt(2.5 * Master->GetAttribute(PERCEPTION));
  const arm* PairArm = GetPairArm();

  if(PairArm && PairArm->IsUsable())
  {
    citem* PairWielded = PairArm->GetWielded();

    if(!PairWielded)
    {
      if(Wielded->IsTwoHanded() && !Wielded->IsShield(Master))
	return Base * (ThisToHit + PairArm->GetAttribute(DEXTERITY)
		       * sqrt(2.5 * Master->GetAttribute(PERCEPTION))) / 2;
    }
    else if(!Wielded->IsShield(Master) && !PairWielded->IsShield(Master))
      return Base * ThisToHit / (1.0 + (500.0 + PairWielded->GetWeight())
				 / (1000.0 + (Wielded->GetWeight() << 1)));
  }

  return Base * ThisToHit;
}

I think it calculates the THV and hence the WeaponTHV in the block. I think it bears resemblance to the parsing shown by Squash, no?
It's getting a bit late for me atm, but hopefully this provides more clues.
#8 Oct 17, 2011, 4:13 pm Hide

fejoa

Also, now I think about it, I think the devs used bitwise operation in place of floating-point operation as often as possible because it may be faster. I'm not a computer engineer but I recall that bitwise operations are a more fundamental structure inside most modern ALU's. Geek geek.
#9 Oct 17, 2011, 6:38 pm Hide

Ischaldirh

Warheck wrote
I agree with all your observations of the code you mentioned here:
if(RAND() % int(100 + WeaponToHitValue / BlockValue / (1 << BlocksSinceLastTurn) * (100 + Success)) < 100)
then (block)



Isn't the THV just the ToHitValue of the weapon/item?

Last night I had gone ahead with the assumption that WeaponToHitValue was the attacking parties' ToHitValue. Even if it belonged to the blocking party, since the both blocker and attacker in my simulation are Guugzamesh, this value would still be ~43.3. I ran this value through, assuming no previous blocks, and came back with the 17% block chance. I'm still not entirely sure this is accurate but I'm going to run with it for now. That gives him a final tally, counting both arms, of having 31% chance to block 18 damage, and roughly 20% chance to block 36. (I don't suppose someone has a lot of time on their hands and is willing to make an extensive empirical test? Where's Z when you need him...)

I do not believe that WeaponToHitValue is the defender's THV. If it were, high THV would actually make blocking MORE difficult, rather than less.

I appreciate the code-diving, Warheck. I'm still pretty confused about the position of certain variables in the code; particularly the position of GetTHVBonus: it's position again seems to indicate the opposite of what is expected, and that plus values actually negatively impact THV (though on a really quite miniscule level). Confusing.

My next task is the real purpose of me bringing this whole thing up: determining just how much more effective blocking with shields is to blocking with weapons, and thus how much more defensive ability is gained by using one over using a second weapon. I'll go to that tonight.
#10 Oct 20, 2011, 4:31 pm Hide

fejoa

Yeah the THV's seem counterintuitive to me too at this stage.

Let me show you another function, this time from item.cpp:

long item::GetBlockModifier() const
{
  if(!IsShield(0))
    return GetSize() * GetRoundness() << 1;
  else
    return GetSize() * GetRoundness() << 2;
}

I don't know how it works because I have not stared at it for long enough. It gets called inside this function in bodypart.cpp,
double arm::GetBlockValue() const { return GetToHitValue() * GetWielded()->GetBlockModifier() / 10000; }
when the game calculates the BlockValue of an arm, for blocking.
It would seem that the blocking value of a shield is modified by its roundness and size.

#11 Oct 20, 2011, 6:54 pm Hide

Ischaldirh

Warheck: You should check out Squash's parsing, linked in my first post; this is in there. However verification is nice.
#12 Oct 27, 2011, 3:22 am Hide

Ischaldirh

I think the biggest game-influencing thing I've learned from this so far is that, while it's probably a good idea to work towards that 10 hit strength cap (10 AStr above the minimum), there's no point training beyond that. If you know you're going to be using a long sword for the rest of the game, you really won't ever need more than ~15 AStr -- and usually you won't even need that much, unless you're using adamant or another high-density material (which there aren't any at high levels).
#13 Oct 27, 2011, 3:26 am Hide

chaostrom

Wouldn't you want more AStr for more damage though?
#14 Oct 27, 2011, 1:53 pm Hide

Ischaldirh

Perhaps; that's another thing I'd like to see, is the code behind damage calculations. As we already saw, HitStrength is capped at 10, so unless damage calculations use a different modifier the same cap may apply.