The simplest way to do random chance events is to generate a random decimal number from 0 to 1, multiplying it by one hundred, and then seeing if that number is less than or equal to the percentage drop chance. Here's some psuedocode:
float random = Random.generateDecimal() * 100;
if(random <= dropChance) /*Give the player the item.*/;
The problem with this is that there is a possibility that your RNG might generate a string of random numbers that results in a number of failures or successes that seems to be disproportionate to the given drop chance even though the outcome was a perfectly possible statistical probability. This will result in either a frustrated player or an unbalanced play session.
I've actually been thinking about this myself for a time, and here is a potential solution:
- Determine the drop chance.
- Calculate the number of trials for which it would be expected that exactly one trial would result in success.
- Randomly generate a list of outcomes that guarantees one success.
- Cycle through that list each time the action is executed.
Here's some psuedocode for making the array for clarity's sake:
float dropChance = 0.25;
int numberOfTrials = 1 / dropChance;
int successfulTrialNumber = (int) (Random.generateDecimal() * numberOfTrials)
boolean[] outcomes = new boolean[numberOfTrials];
for(int i = 0; i < outcomes.length; i++) {
outcomes[i] = (i == successfulTrialNumber);
}
This guarantees that the player won't feel "cheated" while still maintaining enough chance variation to make the outcome seem truly randomly determined. However, the downside to this is that you'll have to keep a separate list of outcomes for each action because different actions have different success rates.
We can fix this by using one array for all of our chance variation events so they don't have to all have the same percent chance of occurring. We'd generate a single master array containing a list of numbers from 1 - 100 (or maybe 1 - 1000 if you wanted some decimal drop chances) and then having all outcomes draw on the next step of this array.
Last bit of pseudocode:
int[] outcomes = new int[100] /*You would fill this array with random numbers from one to one hundred with no repeats.*/
int outcomesPointer = 0;
public void generateOutcome(int percentSuccessChance) {
if(percentSuccessChance <= outcomes[outcomesPointer]) /*Success*/;
outcomesPointer++;
}
This helps to minimize the feeling of "cheapness" while maintaining pseudo randomness.
Sources:
Here's where I learned that true randomness in games can lead to bad outcomes: https://www.youtube.com/watch?v=sLXLlJ7FhJU
The pseudocode solutions were made up by me on the spot.