How to get a 100 Dice Roll to give same output from different method calls.

Discuss modding questions and implementation details.
Post Reply
User avatar
Magicono43
Posts: 1141
Joined: Tue Nov 06, 2018 7:06 am

How to get a 100 Dice Roll to give same output from different method calls.

Post by Magicono43 »

Title probably sucks to explain, but i'm having an issue on a feature i'm adding to my mod. I'm using a new method that returns a bool, the issue is that there are up to 3 different times that this method can be called within a single attack. This would normally not be an issue, but this method has a 100 dice roll that determines the returned bool value, so even if one attack is happening, there is the likely result that one attack is having 2 different roll results, instead of the one attack having the same result from that one attack.


Here is the method:

Code: Select all

		// Checks for if a shield block was successful and returns true if so, false if not.
		private static bool ShieldBlockChanceCalculation(DaggerfallEntity target, bool shieldStrongSpot)
		{
			float blockChance = 0f;
			int targetAgili = target.Stats.LiveAgility - 50;
			int targetSpeed = target.Stats.LiveSpeed - 50;
			int targetStren = target.Stats.LiveStrength - 50;
			int targetEndur = target.Stats.LiveEndurance - 50; // First bug i'm seeing, sometimes this gets ran 2-3 times in a single attack, this makes sense since a bunch of times this method is called, problem is that the result for the roll is different every run.
			int targetWillp = target.Stats.LiveWillpower - 50;
			int targetLuck = target.Stats.LiveLuck - 50;
			
			if (shieldStrongSpot)
			{
				blockChance = 40f; // A base 40% chance of a block added to body parts covered by the shield, might expand on this later.
				blockChance += (targetAgili*.3f);
				blockChance += (targetSpeed*.3f);
				blockChance += (targetStren*.3f);
				blockChance += (targetEndur*.2f);
				blockChance += (targetWillp*.1f);
				blockChance += (targetLuck*.1f);
				
				Mathf.Clamp(blockChance, 7f, 95f);
				int blockChanceInt = (int)Mathf.Round(blockChance);
				
				if (Dice100.SuccessRoll(blockChanceInt))
				{
					Debug.LogFormat("$$$. Shield Blocked A Hard-Point, Chance Was {0}%", blockChanceInt);
					return true;
				}
				else
				{
					Debug.LogFormat("!!!. Shield FAILED To Block A Hard-Point, Chance Was {0}%", blockChanceInt);
					return false;
				}
			}
			else
			{
				blockChance = (targetAgili*.2f);
				blockChance += (targetSpeed*.2f);
				blockChance += (targetStren*.2f);
				blockChance += (targetEndur*.1f);
				blockChance += (targetWillp*.1f);
				blockChance += (targetLuck*.1f);
				
				Mathf.Clamp(blockChance, 0f, 35f);
				int blockChanceInt = (int)Mathf.Round(blockChance);
				
				if (Dice100.SuccessRoll(blockChanceInt))
				{
					Debug.LogFormat("$$$. Shield Blocked A Soft-Point, Chance Was {0}%", blockChanceInt);
					return true;
				}
				else
				{
					Debug.LogFormat("!!!. Shield FAILED To Block A Soft-Point, Chance Was {0}%", blockChanceInt);
					return false;
				}
			}
		}

Edit: A "dirty" solution I was maybe making this method Public and adding a variable that increments every-time the method gets called, and if that value is more than the first call, it will return whatever was returned the last time or something, but i'm not sure how that would work, as well as how I would reset this value back every-time an attack did all of its needed calculations. Like I said, there is the potential that this method is ran 1-3 times from one attack, depending on what is the attacker and if their attack does any damage or not.

User avatar
Ralzar
Posts: 2211
Joined: Mon Oct 07, 2019 4:11 pm
Location: Norway

Re: How to get a 100 Dice Roll to give same output from different method calls.

Post by Ralzar »

You cannot do this: Dice100.SuccessRoll(blockChanceInt)
more than once in the code. Each time rolls the D100 again.

So just do:

bool blocked = Dice100.SuccessRoll(blockChanceInt);


Then use blocked and !blocked in the code instead.

User avatar
Magicono43
Posts: 1141
Joined: Tue Nov 06, 2018 7:06 am

Re: How to get a 100 Dice Roll to give same output from different method calls.

Post by Magicono43 »

Ralzar wrote: Fri Apr 03, 2020 1:59 pm You cannot do this: Dice100.SuccessRoll(blockChanceInt)
more than once in the code. Each time rolls the D100 again.

So just do:

bool blocked = Dice100.SuccessRoll(blockChanceInt);


Then use blocked and !blocked in the code instead.
Yeah, I think i'll have to do that, and just pass the bool result to the other "potential" branches of other methods that could get called, that's probably the easiest way to do it. I'll try it out and see how the result turns out, thanks.

User avatar
Hazelnut
Posts: 3016
Joined: Sat Aug 26, 2017 2:46 pm
Contact:

Re: How to get a 100 Dice Roll to give same output from different method calls.

Post by Hazelnut »

Maybe I have the wrong end of the stick here, but surely you just change the calling code to call this once and keep the result in a variable for use in the 3 places?
See my mod code for examples of how to change various aspects of DFU: https://github.com/ajrb/dfunity-mods

User avatar
Magicono43
Posts: 1141
Joined: Tue Nov 06, 2018 7:06 am

Re: How to get a 100 Dice Roll to give same output from different method calls.

Post by Magicono43 »

Hazelnut wrote: Fri Apr 03, 2020 2:33 pm Maybe I have the wrong end of the stick here, but surely you just change the calling code to call this once and keep the result in a variable for use in the 3 places?
Yeah, that's what i'm doing now, I turned the "shieldBlockSuccess" variable into,

Code: Select all

public static bool shieldBlockSuccess { get; set; }
and call the method once and use the "global" bool variable in all the other places it was previously used. So this rework actually makes the code more "efficient" than before. Or at the very least it reduces the number of lines of code.

User avatar
DigitalMonk
Posts: 111
Joined: Sun Nov 10, 2019 8:01 pm

Re: How to get a 100 Dice Roll to give same output from different method calls.

Post by DigitalMonk »

As mentioned by Ralzar and Hazelnut, the generic solution to "I need to reuse the result of this function call multiple times" is to store that result in a variable and use the variable. This is useful both in cases like yours, where it is a necessity for logical correctness, and in cases where function calls are computationally expensive in some way. I only mention this because it's an important programming tip in the general sense of programming, unrelated to specifics of pseudo random number generation (PRNG).

Spoiler!
(Technical tangent here... Just skip over this paragraph unless you enjoy esoterica.)

I want to generalize away from PRNG, because technically you _can_ get the same value from a PRNG function by giving it the same 'seed' value for each call. But that IS NOT WHAT YOU WANT in this case. Re-seeding a PRNG is really only useful when you need a SERIES of "random-looking" values that are nevertheless the same every time you need them. Terrain generation uses this technique, by using location as the seed value, which then gives you a "random-looking" series of values you can use for height, vegetation, water content, rockiness, whatever, and because you seeded it with location, that location will always give you the same series of values.

(The following is mostly me thinking out loud about library interface design. Feel free to ignore it. Sometimes I can't help myself.)
Something else that occurs to me is that I'm not sure how useful SuccessRoll() is as an interface. The only thing it adds beyond Roll() is a comparison. I suppose it reads more cleanly than "Dice100.Roll() <= blockChanceInt", and it does reduce the potential for errors caused by using < instead of <= or thinking about it backwards and using > or >=...

So, it is useful. The only reason I even considered it is that I can see a world of "fuzzy success and failure" that couldn't be implemented with SuccessRoll(), but could be done with Roll() if you stored the resulting roll value. I'm thinking of something where success isn't black or white, but has a gray region. Say your blockChanceInt is 65. If you roll 66, yep, you failed. But maybe rolling 65 doesn't fully block -- maybe it's a 10% block. And you get an additional 10% for each point under blockChanceInt, so that a roll of 60 gives 50% block and a roll of 55 gives 100% block. (Of course, you could also say that blockChanceInt is the chance of 100% block with a fading ramp at higher rolls, or that blockChanceInt is the 50% point. Up to your design preferences). If you went with such a system, you'd need access to the Roll() result directly. Of course, it would be better to put that fuzzy logic inside a function, perhaps something like (pardon my syntax, my C# is rusty) (also, I'm NOT going to be pedantic about off-by-one errors here, so don't trust this code explicitly, it's just a sketch):

Code: Select all

float SuccessFraction(int chance, int range)  {
	int roll = Roll();
	if (roll > chance) { return 0.0f; }  // Total failure
	
	int distance = chance-roll;
	if (distance > range) { return 1.0f; }  // Total success
	
	return (float)distance/(float)range;	// Partial success
}
This provides a few benefits:
  • You can directly use the success fraction to scale the effect. For instance, if a completely successful strike should do 10 damage, you can do 10*SuccessFraction and it scales fuzzily for you.
  • You can implement razor-sharp, black-or-white success by giving a range of 0 when you call. The result will always be 1.0f or 0.0f.
  • If you don't like linear scaling, you could run this through, say, a sine wave or a sigmoid function, or whatever. Gotta be a little careful with offsets, but something like "0.5f + Sin((fraction-0.5f)*Pi/2)/2"

User avatar
DigitalMonk
Posts: 111
Joined: Sun Nov 10, 2019 8:01 pm

Re: How to get a 100 Dice Roll to give same output from different method calls.

Post by DigitalMonk »

Magicono43 wrote: Fri Apr 03, 2020 4:29 pm Yeah, that's what i'm doing now, I turned the "shieldBlockSuccess" variable into,

Code: Select all

public static bool shieldBlockSuccess { get; set; }
That worries me because it adds publicly visible state that could get stale very easily. Do you really need anything more than a local variable inside the shield block testing function? It's a local cache. Putting it in a member variable like this implies that the state of the object has changed -- that somehow this object is now semi-permanently in a "successfully blocking" state. That seems like too long of a scope/lifetime for this result...

User avatar
Magicono43
Posts: 1141
Joined: Tue Nov 06, 2018 7:06 am

Re: How to get a 100 Dice Roll to give same output from different method calls.

Post by Magicono43 »

DigitalMonk wrote: Fri Apr 03, 2020 4:36 pm Something else that occurs to me is that I'm not sure how useful SuccessRoll() is as an interface. The only thing it adds beyond Roll() is a comparison. I suppose it reads more cleanly than "Dice100.Roll() <= blockChanceInt", and it does reduce the potential for errors caused by using < instead of <= or thinking about it backwards and using > or >=...
Thanks for the extra info on this here. I really have no clue of the differences between the types of "Rolls" used. I just copy-pasted a roll from the FormulaHelper.cs that looked like it would work for what I was trying to do and just used that one. On the other part of your post, I could definitely find that useful for some other implementations, but for this current one i'm mostly alright with a "Black or White" sort of thing, just like how the hit-chance works out. I mostly wanted to add this blockchance feature because without it, the shield would block all attacks onto the body parts it would cover, essentially making armor on those parts of the body pointless while using a shield.

So with making it so there is a chance of failure for the shield to take the hit, it actually makes wearing a full set of armor better than just wearing a half set or something. I also plan on changing a few other things with this change, but i'm currently fine with a black or white roll.

User avatar
DigitalMonk
Posts: 111
Joined: Sun Nov 10, 2019 8:01 pm

Re: How to get a 100 Dice Roll to give same output from different method calls.

Post by DigitalMonk »

Magicono43 wrote: Fri Apr 03, 2020 4:55 pm I really have no clue of the differences between the types of "Rolls" used. I just copy-pasted a roll from the FormulaHelper.cs that looked like it would work for what I was trying to do and just used that one.
I'm probably a horrible poster... I've not looked inside the DFU code base and I have a tendency to rattle off stuff as it pours out of my head :oops: Gives me a nice break from my day job, and I just hope that occasionally something I say stirs someone else's design thoughts.

Post Reply