Page 1 of 1

Unintended results in monster spawn range calculations

Posted: Sun Dec 08, 2019 7:05 am
by Rand
Jehuty asked me to look at some code. Something bothered me about it and after thinking for a while, I noticed this:

The code is from RandomEncounters.cs
https://github.com/Interkarma/daggerfal ... s.cs#L1476

First, to those who aren't coders, bear in mind that the first slot on an enemy list is number 0, not 1. (the 20th slot then corresponds to the number 19, not 20)

Okay, so to generate an enemy it essentially "rolls" a number from 1 to 100.

If the rolled number is 1 to 80 (80% chance), it sets a range of numbers from the player level -3 to player level +3.

If the rolled number is from 81 to 95 (15% chance) it sets a range of numbers from 0 to player level +1.
EDIT: This next section is wrong, please ignore it. :oops:
THERE IS (was) A POTENTIAL GLITCH HERE IF THE PLAYER LEVEL IS 19 OR HIGHER: the generated max will be 20 or higher. This would normally be a significant problem, but see below.

If the rolled number is 96 to 100 (5% chance), and if the player level is 5 or less, it sets a range of numbers from 0 to player level +2. If the player level is 6 or higher it sets a range of numbers from 0 to 19. (That means any monster on the list. Hello Vampire Ancients, Ancient Liches, and Daedra Lords. At level 6 :twisted:)

Then it looks at those max and min numbers. If the minimum or maximum number of the range turn out to be less than 0 or over 19, the ranges become fixed: 0 to 5 if the min went below 0, or 14 to 19 if the max went over 19. (It can't possibly do both.)

Lastly, it picks one number from the range and looks on a list specific to that area to see what monster to place.

EDIT: This next section is wrong, please ignore it. :oops:
As to the issue in the 15% chance condition. Normally there would be a serious error with a possible too high max value at player level 19, because the lists are only 20 elements long and so it can't choose one if the generated number ends up being 20 or higher. I have no idea what the result of that would be. A crash, maybe?

But some code added by DFU (for a different purpose!) at line 1517 seems to accidentally catch the excessive value in that case.

EDITED: removed unnecessary explanations

EDIT: This next section is now unnecessary, please ignore it. :oops:
Altering the code can fix this quite easily. One solution is to add some lines between 1495 and 1496:

if (max > 19)
{
max = 19;
}

But this only fixes the level 19+ problem.


The DFU code (lines 1517 to 1524) produces incorrect results to the intended ranges generated by classic code, lines 1477 to 1512.

Now, the DFU code shouldn't normally come into use unless the enemy list is shorter (or possibly: longer) than 20 entries (which, at present, none are).

But in my opinion, at minimum, line 1521 (min = max - 5;) needs to be rewritten anyway, so as to properly set min to reflect the min set by the classic conditions (level -3 (minimum 0) or level 0, depending on which condition between lines 1477 to 1512 was passed).

This naturally increases the frequency of higher level spawns for level 19+ characters when this code comes into use.

Is this a major problem? No, but it is an unintended consequence.

There are several pretty obvious options/solutions, but I won't presume to write the code for you guys. However, if I were doing it, I would re-write the lines from 1477 to 1512 without assuming 20 element lists (both longer and shorter ones) and remove the (now unnecessary) DFU code from lines 1517 to 1524. This would solve all possible issues at present and in future.

My thanks to Jehuty to bringing this to my notice. :)

Re: Unintended results in monster spawn range calculations

Posted: Sun Dec 08, 2019 7:50 am
by Ralzar
And if DFU didn’t have a 20 limit on the lists, we could do more with modding it :)

Re: Unintended results in monster spawn range calculations

Posted: Sun Dec 08, 2019 8:48 am
by pango
Seems to match my own previous analysis, alternate random enemy selection is currently broken in endgame.
I simplified nested ifs by switching to mathematical expectations, which is what those heatmaps are based on.

Re: Unintended results in monster spawn range calculations

Posted: Sun Dec 08, 2019 10:49 am
by Interkarma
I'm happy for this be reviewed. My main requirement is that any change still generates classic enemy loadout, especially in Privateer's Hold upon starting a new game. My early placeholder enemy selection generated different enemy loadouts than classic, and I received many complaints about it. That all stopped when Allofich implemented the current version, which matches classic other than the flaw mentioned.

If nobody else wants to take this on (Pango seems to have some knowledge and interest of this already), then I can take a chop later on.

I'll move this to developer discussion as that's a more appropriate spot for it.

Re: Unintended results in monster spawn range calculations

Posted: Sun Dec 08, 2019 11:53 am
by pango
Actually I needed to reread your first post more carefully, because you're talking of a somewhat similar issue, but in classic enemy selection code.

Well, test line 1517 is currently never triggered with the classic 20-length encounter tables, because max is already clamped to 19 line 1508, a check you somehow seems to have missed. That's also why I did not take into account this DFU specific code in my own analysis.

It will only become an issue with shorter encounter tables, if we decide to use some. Simplest way to extend the classic algorithm to any other encounter table lengths would be to replace code between lines 1508 and 1524 with

Code: Select all

int maxIndex = EncounterTables[encounterTableIndex].Length - 1;
if (max > maxIndex)
{
  min = maxIndex > 5 ? maxIndex - 5 : 0;
  max = maxIndex;
}
(if maxIndex is not > 5, the whole algorithm degenerates to a random selection over all available enemies though)

Re: Unintended results in monster spawn range calculations

Posted: Sun Dec 08, 2019 7:08 pm
by Rand
Yes, Pango, you're correct. The clamp code from 1503 to 1512 does catch all excessively high and low values in classic.

My only defense is that I was working very late at night, and consequently my underperforming brain associated lines 1503 to 1512 solely with the else conditional at 1498 to 1502 (the 80% chance -3 to +3 range).

Looking at it now after 10 hours of sleep, I see it immediately. :oops:

So while the potential bug was a sleep-deprivation phantom (and the potential fix I suggested is unnecessary), the rest of the post is essentially correct, and your proposal for the DFU code patch is a good one.

A comment in the encounter tables section that lists must not be shorter than 6 elements (or whatever the code eventually assumes) would be a good idea, too.