3
\$\begingroup\$

I have created a simple percentage-based random prize drop for killing certain mobs. A drop is basically what the player will get in return for killing a mob, as a reward.

So I have a large list of Mobs saved in an XML file which looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<drops>
    <npc id="1">
        <drop item="175" amount="1" chance="0.001" />
        <drop item="174" amount="5" chance="0.013" />
        <drop item="173" amount="1" chance="0.158" />
        <drop item="172" amount="1" chance="0.098" />
    </npc>
</drops>

Basically each NPC has its own ID, so that way we can easily decide what drops does a certain NPC can drop.

So for NPC '1' we have 4 drops, the hardest to get drop is 0.001 (1/1000) and the easiest one is 0.158.

To make the drop system percentage based, I used SecureRandom and the nextFloat() method to generate the base drop rate for the NPC.

After I got the rate, I go through all of the drops that the NPC can drop, and find the drop with the closest drop chance rate to the generated random float rate.

I do this like that:

/**
 * Gets a random drop for NPC by random percentage
 * @param npc       The NPC id
 * @return  The drop details
 */
public Drop getRandomDrop(int npc) {
    List<Drop> drops = this.drops.get(npc);

    /**
     * The random drop rate
     */
    float rate = random.nextFloat();

    /**
     * The closest distance to rate
     */
    float bestDistance = Float.MAX_VALUE;

    // Closest's drop instance
    Drop closest = null;

    for (Drop d : drops) {
        float rt = d.getChance();
        if (rt == rate) {
            return d;
        }

        float distance = Math.abs(rt - rate);
        if (distance < bestDistance) {
            bestDistance = distance;
            closest = d;
        }
    }

    return closest;
}

But I feel that I can make the drops system more accurate, as in this approach is not accurate enough. I need some reviews and suggestions on my approach.

\$\endgroup\$
  • \$\begingroup\$ I'd like to see the initialization of random and the code for d.getChance(). Depending on what that says, I may want more code after that. It would help if you could explain what you mean when you say that this approach is not accurate enough. \$\endgroup\$ – Brythan Nov 13 '14 at 19:12
  • \$\begingroup\$ @Brythan There's nothing to see, really, d.getChance() just returns the float number that was loaded from the attribute chance="0.001" etc and random is SecureRandom random = new SecureRandom() \$\endgroup\$ – user3123545 Nov 13 '14 at 19:15
3
\$\begingroup\$
/**
 * Gets a random drop for NPC by random percentage
 * @param npc       The NPC id
 * @return  The drop details
 */
public Drop getRandomDrop(int npc) {
    List<Drop> drops = this.drops.get(npc);

    float roll = random.nextFloat();

    for ( Drop d : drops ) {
        float chance = d.getChance();
        if ( roll < chance ) {
            return d;
        }

        roll -= chance;
    }

    // if no drop was selected, return an empty drop
    return Drop::EMPTY_DROP;
}

This is how I would write it. Note that I changed the random variable to be called roll like a roll of a die. For each drop, it compares the roll to the chance of getting that drop. If the roll is less than the drop rate, then that drop is the loot. Otherwise, reduce the roll by the drop chance (which makes subsequent drops more likely).

This method assumes that the drop chances in the XML add up to 1 or that there is an implicit chance of an empty drop. If you want to always drop and have drop chances that don't add up to 1, you can normalize the rate.

float totalChance = 0.0;
for ( Drop d : drops ) {
    totalChance += d.getChance();
}

and then in the current loop

        float chance = d.getChance() / totalChance;

You may still have to do some adjustment to get the rounding right in the case of a maximum random.nextFloat result.

You would set the EMPTY_DROP to be a static class constant representing no loot. Note that this would also be the drop if the NPC's section was missing from the XML file.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ this is what I'd do as well, instead of choosing a single drop, I would give each sub-drop (gold, itemA, itemB) a separate chance. perhaps if there is a limit of "only 1 item" you could put items in a group. then put a % chance to get the group. (say common item drop: 25% rare item drop 5%) and then do a second roll to find out which common item / rare item you get. \$\endgroup\$ – Joeblade Nov 19 '14 at 0:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.