There's a lot of ways to do it. There's a lot of ways to "tweak" each way to do it. Ultimately drop determination is always an adhoc system, like map generation. There is a particularly cool algorithm for making a random choice from a set with predetermined proportional rarities, but you'll probably still want to find a way to "adjust" them to suit your ideas about what should be possible.
That said, the cool algorithm is the alias method.
Suppose you want to sample from N different items, with relative probabilities p[0],...,p[N-1] summing to M. You do the preprocessing I'll describe below to generate this table. To take a sample, pick one of the N entries in the table at random (according to a uniform distribution). Each entry in the table contains two item numbers a and b, and an integer w. Now pick a random number from 0..M-1. If this number is smaller than w, you return the item in a, otherwise you return the one in b.
Here's how you set up the table.
First, multiply each p by N so that we can do everything in integer arithmetic. Store all item numbers to be selected from in a data structure S. Then, for k=0..N-1, fill the entries as follows: Select the item i from S with smallest p, and the item j from S with largest p[j].
Put i in a[k], j in b[k], and p in w[k]. This gives item i a probability of w[k] out of MN to be sampled using the table, so we are done with it and removed it from S. Item j remains in S, and p[j] is reduced by M-w[k].
To see why it works, you have to realize that p <= M <= p[j] at every step.
Example: we have seven items with relative probabilities A=1,B=2,C=2,D=3,E=4,F=9,G=9. This means B or C are twice as likely as A, E is twice as likely as B or C, G is 3 times as likely as D, and so on. This is 30 oddments. An "oddment" is a funny word meaning an equal share of probability.
We create a table S with 7 entries.
We multiply all oddments by 7 so we work can work with integers.
Our list is now
A=7,B=14,C=14,D=21,E=28,F=63,G=63. This is 210 oddments, but since the numbers are in the same proportion to each other as before, it's the same probabilities for each item.
Now, since we have 7 buckets, and 210/7 = 30, each bucket represents 30 oddments.
bucket 0 gets 7 oddments of A (which is all there is) and 23 oddments of F.
our list is now:
B=14, C=14, D=21, E=28, F=40, G=63
bucket 1 gets 14 oddments of B and 16 oddments of G.
our list is now:
C=14, D=21, E=28, F=40, G=47
Bucket 2 gets 14 oddments of C and 16 oddments of G.
our list is now:
D=21, E=28, F=40, G=31
Bucket 3 gets 21 oddments of D and 9 oddments of F.
our list is now:
E=28, F=31, G=31
Bucket 4 gets 28 oddments of E and 2 oddments of F.
our list is now:
F=29, G=31
Bucket 5 gets 29 oddments of F and 1 oddment of G.
our list is now
G=30.
So that's what bucket 6 gets. :-)
So when we want an item, we generate a number from 0 to 6 inclusive to pick a bucket, then generate a number from 0 to 29 inclusive to check which of the (at most 2) choices in that bucket we get.
This gives you item selection from a set of items with fixed probabilities, in a way that has a number of desirable properties (runs in constant time, cannot hang continuing to retry, etc). So you can make your epic celestial angelical allmighty scythe of destruction +20 very very rare, and your simple dagger very common, and that will solve the most basic of your problems.
Of course you're going to want to tweak it. In practice, you'll probably build a different alias-method selection table every time you go to a new floor of the dungeon to enforce the "more powerful items deeper" thing. Alternatively, or additionally, you can develop different tables to generate drops from different types of creature so as to keep them thematic.
Bear