.gitignore: Also ignore Metapost output.
[dnd] / dice.lisp
index 61f99d8..ee031fa 100644 (file)
--- a/dice.lisp
+++ b/dice.lisp
        (when (< (random n) 1)
          (setf it (car things))))))
 
-(defmacro pick (&rest clauses)
+(defmacro pick (&body clauses)
   `(funcall (choose ,@(loop for (n . clause) in clauses
                           collect n
                           collect `(lambda () ,@clause)))))
 
-(defconstant druid-spells
-  #((detect-danger faerie-fire locate predict-weather)
-    (heat-metal obscure produce-fire warp-wood)
-    (call-lightning hold-animal protection-from-poison water-breathing)
-    (control-temperature-10-ft-radius plant-door protection-from-lightning
-     summon-animals)
-    (anti-plant-shell control-winds dissolve pass-plant)
-    (anti-animal-shell summon-weather transport-through-plants turn-wood)
-    (creeping-doom metal-to-wood summon-elemental weather-control)))
+(defmacro pick-matching ((form &key) &body clauses)
+  (let ((formtemp (gensym "FORM")))
+    `(let ((,formtemp ,form))
+       (pick ,@(loop for (prob assertion . code) in clauses
+                    collect `((if (assertion-match-p ,formtemp ',assertion)
+                                  ,prob
+                                  0)
+                              ,@code))))))
 
 (defconstant cleric-spells
   #((cure-light-wounds detect-evil detect-magic light protection-from-evil
     (earthquake holy-word raise-dead-fully restore survival travel wish
      wizardry)))
 
+(defconstant druid-only-spells
+  #((detect-danger faerie-fire locate predict-weather)
+    (heat-metal obscure produce-fire warp-wood)
+    (call-lightning hold-animal protection-from-poison water-breathing)
+    (control-temperature-10-ft-radius plant-door protection-from-lightning
+     summon-animals)
+    (anti-plant-shell control-winds dissolve pass-plant)
+    (anti-animal-shell summon-weather transport-through-plants turn-wood)
+    (creeping-doom metal-to-wood summon-elemental weather-control)))
+
+(defconstant druid-spells
+  (make-array 7 :initial-contents (loop for cs across cleric-spells
+                                       for ds across druid-only-spells
+                                       collect (append cs ds))))
+
 (defconstant magic-user-spells
   #((analyse charm-person detect-magic floating-disc hold-portal light
      magic-missile protection-from-evil read-languages read-magic shield
     (contingency create-any-monster gate heal immunity maze meteor-swarm
      power-word-kill prismatic-wall shapechange survival timestop wish)))
 
-(defun spell-caster-type ()
-  (choose 25 :cleric 5 :druid 70 :magic-user))
+(defun spell-caster-type (&optional (form :any))
+  (pick-matching (form)
+    (5 (:user (:cleric :druid :paladin)) :cleric)
+    (1 (:user :druid) :druid)
+    (14 (:user (:magic-user :elf :thief)) :magic-user)))
 
 (defun random-spell (&optional (caster (spell-caster-type))
                               (level (ecase caster
   (let ((list (aref (ecase caster
                      ((:magic-user) magic-user-spells)
                      ((:cleric) cleric-spells)
-                     ((:druid) druid-spells))
+                     ((:druid) druid-only-spells))
                    level)))
     (values (elt list (random (length list)))
            caster
            level)))
 
-(defun symbol-match-p (form sym)
-  (cond ((eq form t) t)
-       ((eq form nil) nil)
-       ((eq form sym) t)
-       ((atom form) nil)
-       (t (ecase (car form)
-            ((and) (every (lambda (f) (symbol-match-p f sym)) (cdr form)))
-            ((or) (some (lambda (f) (symbol-match-p f sym)) (cdr form)))
-            ((not) (not (symbol-match-p (cadr form) sym)))))))
+(let ((magic (list :magic)))
+  (defun assertion-match-p (form assertions)
+    (cond ((eq form :any) t)
+         ((eq form :none) nil)
+         ((atom form) (if (atom assertions)
+                          (eql form assertions)
+                          (member form assertions)))
+         (t (case (car form)
+              ((and) (every (lambda (f)
+                              (assertion-match-p f assertions))
+                            (cdr form)))
+              ((or) (some (lambda (f)
+                            (assertion-match-p f assertions))
+                          (cdr form)))
+              ((not) (not (assertion-match-p (cadr form) assertions)))
+              (t (let ((sub (getf assertions (car form) magic)))
+                   (if (eq sub magic)
+                       t
+                       (assertion-match-p (cadr form) sub)))))))))
 
 (defun choose-distinct-items (n seq)
   (let* ((copy (subseq (coerce seq 'vector) 0))
        (setf (aref copy j) (aref copy len))))))
 
 (defun magic-item (form)
-  (labels ((potion (&key recursivep)
-            (pick (2 `(:potion agility))
-                  (1 `(:potion animal-control))
-                  (3 `(:potion antidote))
-                  (2 `(:potion blending))
-                  (2 `(:potion bug-repellent))
-                  (2 `(:potion clairaudience))
-                  (2 `(:potion clairvoyance))
-                  (2 `(:potion climbing))
-                  (2 `(:potion defence :bonus ,(choose 3 1
-                                                        2 2
-                                                        2 3
-                                                        2 4
-                                                        1 5)))
-                  ((if recursivep 0 4)
-                   `(:potion delusion
-                     :fakes ,@(cdr (potion :recursivep t))))
-                  (2 `(:potion diminution))
-                  (1 `(:potion ,(choose 35 'white-dragon-control
-                                        15 'crystal-dragon-control
-                                        35 'black-dragon-control
-                                        15 'onyx-dragon-control
-                                        28 'green-dragon-control
-                                        12 'jade-dragon-control
-                                        21 'blue-dragon-control
-                                         9 'sapphire-dragon-control
-                                        14 'red-dragon-control
-                                         6 'ruby-dragon-control
-                                         7 'gold-dragon-control
-                                         3 'amber-dragon-control)))
-                  (2 `(:potion dreamspeech))
-                  (1 `(:potion elasicity))
-                  (2 `(:potion ,(choose-uniformly 'air-form
-                                                  'water-form
-                                                  'earth-form
-                                                  'fire-form)))
-                  (2 `(:potion esp))
-                  (1 `(:potion ethereality))
-                  (3 `(:potion fire-resistance))
-                  (3 `(:potion flying))
-                  (2 `(:potion fortitude))
-                  (1 `(:potion freedom))
-                  (3 `(:potion gaseous-form))
-                  (1 `(:potion ,(choose 5 'hill-giant-control
-                                        5 'stone-giant-control
-                                        4 'frost-giant-control
-                                        2 'fire-giant-control
-                                        1 'mountain-giant-control
-                                        1 'sea-giant-control
-                                        1 'cloud-giant-control
-                                        1 'storm-giant-control)))
-                  (3 `(:potion giant-strength))
-                  (2 `(:potion growth))
-                  (6 `(:potion healing))
-                  (3 `(:potion heroism))
-                  (1 `(:potion human-control))
-                  (3 `(:potion invisibility))
-                  (2 `(:potion invulnerability))
-                  (2 `(:potion levitation))
-                  (2 `(:potion longevity))
-                  (1 `(:potion luck))
-                  (1 `(:potion merging))
-                  (2 `(:potion plant-control))
-                  (3 `(:potion poison))
-                  (3 `(:potion polymorph-self))
-                  (2 `(:potion sight))
-                  (2 `(:potion speech))
-                  (4 `(:potion speed))
-                  (2 `(:potion strength))
-                  (3 `(:potion super-healing))
-                  (3 `(:potion swimming))
-                  (1 `(:potion treasure-finding))
-                  (1 `(:potion undead-control))
-                  (2 `(:potion water-breathing))))
+  (labels ((cursedp (&optional (prob 10))
+            (cond ((assertion-match-p form '(:cursed :unspecified))
+                   (zerop (random prob)))
+                  ((assertion-match-p form '(:cursed nil))
+                   nil)
+                  (t t)))
+          (potion (&key recursivep)
+            (pick-matching (form)
+              (2 (:cursed nil) `(:potion agility))
+              (1 (:cursed nil) `(:potion animal-control))
+              (3 (:cursed nil) `(:potion antidote))
+              (2 (:cursed nil) `(:potion blending))
+              (2 (:cursed nil) `(:potion bug-repellent))
+              (2 (:cursed nil) `(:potion clairaudience))
+              (2 (:cursed nil) `(:potion clairvoyance))
+              (2 (:cursed nil) `(:potion climbing))
+              (2 (:cursed nil) `(:potion defence :bonus ,(choose 3 1
+                                                                 2 2
+                                                                 2 3
+                                                                 2 4
+                                                                 1 5)))
+              ((if recursivep 0 4) (:cursed t)
+               (setf form :any)
+               `(:potion delusion
+                 :fakes ,@(cdr (potion :recursivep t))))
+              (2 (:cursed nil) `(:potion diminution))
+              (1 (:cursed nil) `(:potion ,(choose 35 'white-dragon-control
+                                                  15 'crystal-dragon-control
+                                                  35 'black-dragon-control
+                                                  15 'onyx-dragon-control
+                                                  28 'green-dragon-control
+                                                  12 'jade-dragon-control
+                                                  21 'blue-dragon-control
+                                                  9 'sapphire-dragon-control
+                                                  14 'red-dragon-control
+                                                  6 'ruby-dragon-control
+                                                  7 'gold-dragon-control
+                                                  3 'amber-dragon-control)))
+              (2 (:cursed nil) `(:potion dreamspeech))
+              (1 (:cursed nil) `(:potion elasicity))
+              (2 (:cursed nil) `(:potion ,(choose-uniformly 'air-form
+                                                            'water-form
+                                                            'earth-form
+                                                            'fire-form)))
+              (2 (:cursed nil) `(:potion esp))
+              (1 (:cursed nil) `(:potion ethereality))
+              (3 (:cursed nil) `(:potion fire-resistance))
+              (3 (:cursed nil) `(:potion flying))
+              (2 (:cursed nil) `(:potion fortitude))
+              (1 (:cursed nil) `(:potion freedom))
+              (3 (:cursed nil) `(:potion gaseous-form))
+              (1 (:cursed nil) `(:potion ,(choose 5 'hill-giant-control
+                                                  5 'stone-giant-control
+                                                  4 'frost-giant-control
+                                                  2 'fire-giant-control
+                                                  1 'mountain-giant-control
+                                                  1 'sea-giant-control
+                                                  1 'cloud-giant-control
+                                                  1 'storm-giant-control)))
+              (3 (:cursed nil) `(:potion giant-strength))
+              (2 (:cursed nil) `(:potion growth))
+              (6 (:cursed nil) `(:potion healing))
+              (3 (:cursed nil) `(:potion heroism))
+              (1 (:cursed nil) `(:potion human-control))
+              (3 (:cursed nil) `(:potion invisibility))
+              (2 (:cursed nil) `(:potion invulnerability))
+              (2 (:cursed nil) `(:potion levitation))
+              (2 (:cursed nil) `(:potion longevity))
+              (1 (:cursed nil) `(:potion luck))
+              (1 (:cursed nil) `(:potion merging))
+              (2 (:cursed nil) `(:potion plant-control))
+              (3 (:cursed t) `(:potion poison))
+              (3 (:cursed nil) `(:potion polymorph-self))
+              (2 (:cursed nil) `(:potion sight))
+              (2 (:cursed nil) `(:potion speech))
+              (4 (:cursed nil) `(:potion speed))
+              (2 (:cursed nil) `(:potion strength))
+              (3 (:cursed nil) `(:potion super-healing))
+              (3 (:cursed nil) `(:potion swimming))
+              (1 (:cursed nil) `(:potion treasure-finding))
+              (1 (:cursed nil) `(:potion undead-control))
+              (2 (:cursed nil) `(:potion water-breathing))))
           (scroll ()
-            (pick (3 `(:scroll communication))
-                  (2 `(:scroll creation))
-                  (8 `(:scroll curse))
-                  (1 (multiple-value-bind
-                         (spell caster level)
-                         (random-spell)
-                       (declare (ignore level))
-                       `(:scroll delay :caster ,caster :spells (,spell))))
-                  (3 `(:scroll equipment
-                       :items ,(choose-distinct-items 6
-                                                      '(grappling-hook
-                                                        hammer
-                                                        iron-spikes
-                                                        lantern
-                                                        mirror
-                                                        wooden-pole
-                                                        rope
-                                                        saddle
-                                                        backpack
-                                                        saddle-bags
-                                                        stakes-and-mallet
-                                                        wolfsbane))))
-                  (2 `(:scroll illumination))
-                  (2 `(:scroll mages))
-                  (4 `(:map normal-treasure))
-                  (3 `(:map magical-treasure))
-                  (2 `(:map combined-treasure))
-                  (1 `(:map special-treasure))
-                  (3 `(:scroll mapping))
-                  (2 `(:scroll portals))
-                  (6 `(:scroll protection-from-elementals))
-                  (8 `(:scroll protection-from-lycanthropes))
-                  (4 `(:scroll protection-from-magic))
-                  (7 `(:scroll protection-from-undead))
-                  (2 `(:scroll questioning))
-                  (1 (multiple-value-bind
-                         (spell caster level)
-                         (random-spell)
-                       `(:scroll repetition
-                         :caster ,caster
-                         :level ,level
-                         :spells (,spell))))
-                  (2 `(:scroll seeing))
-                  (2 `(:scroll shelter))
-                  (3 `(:scroll spell-catching :max-level ,(choose 4 1
-                                                                  3 2
-                                                                  2 3
-                                                                  1 8)))
-                  (25 (let ((caster (spell-caster-type))
-                            (spells (choose 50 1  33 2  17 3)))
-                        `(:scroll spell
-                          :caster ,caster
-                          :spells ,(loop repeat spells
-                                         collect (random-spell caster)))))
-                  (2 `(:scroll trapping))
-                  (2 `(:scroll truth))))
+            (pick-matching (form)
+              (3 (:cursed nil) `(:scroll communication))
+              (2 (:cursed nil) `(:scroll creation))
+              (8 (:cursed t) `(:scroll curse))
+              (1 (:user (:cleric :druid :magic-user :elf :thief :paladin)
+                  :cursed nil)
+                 (multiple-value-bind
+                     (spell caster level)
+                     (random-spell (spell-caster-type form))
+                   (declare (ignore level))
+                   `(:scroll delay :caster ,caster :spells (,spell))))
+              (3 (:cursed nil)
+                 `(:scroll equipment
+                   :items ,(choose-distinct-items 6
+                                                  '(grappling-hook
+                                                    hammer
+                                                    iron-spikes
+                                                    lantern
+                                                    mirror
+                                                    wooden-pole
+                                                    rope
+                                                    saddle
+                                                    backpack
+                                                    saddle-bags
+                                                    stakes-and-mallet
+                                                    wolfsbane))))
+              (2 (:cursed nil) `(:scroll illumination))
+              (2 (:cursed nil :user (:magic-user :cleric :druid :elf))
+                 `(:scroll mages))
+              (4 (:cursed nil) `(:map normal-treasure))
+              (3 (:cursed nil) `(:map magical-treasure))
+              (2 (:cursed nil) `(:map combined-treasure))
+              (1 (:cursed nil) `(:map special-treasure))
+              (3 (:cursed nil) `(:scroll mapping))
+              (2 (:cursed nil) `(:scroll portals))
+              (6 (:cursed nil) `(:scroll protection-from-elementals))
+              (8 (:cursed nil) `(:scroll protection-from-lycanthropes))
+              (4 (:cursed nil) `(:scroll protection-from-magic))
+              (7 (:cursed nil) `(:scroll protection-from-undead))
+              (2 (:cursed nil) `(:scroll questioning))
+              (1 (:cursed nil
+                  :user (:cleric :druid :magic-user :elf :thief :paladin))
+                 (multiple-value-bind
+                     (spell caster level)
+                     (random-spell (spell-caster-type form))
+                   `(:scroll repetition
+                     :caster ,caster
+                     :level ,level
+                     :spells (,spell))))
+              (2 (:cursed nil) `(:scroll seeing))
+              (2 (:cursed nil) `(:scroll shelter))
+              (3 (:cursed nil)
+                 `(:scroll spell-catching :max-level ,(choose 4 1
+                                                              3 2
+                                                              2 3
+                                                              1 8)))
+              (25 (:cursed nil
+                   :user (:cleric :druid :magic-user :elf :thief :paladin))
+                  (let ((caster (spell-caster-type form))
+                        (spells (choose 50 1  33 2  17 3)))
+                    `(:scroll spell
+                      :caster ,caster
+                      :spells ,(loop repeat spells
+                                     collect (random-spell caster)))))
+              (2 (:cursed nil) `(:scroll trapping))
+              (2 (:cursed nil) `(:scroll truth))))
           (wand-charges () (d 10 3))
           (staff-charges () (d 20 2))
           (wandlike ()
-            (pick (5 `(:wand cold :charges ,(wand-charges)))
-                  (5 `(:wand enemy-detection :charges ,(wand-charges)))
-                  (4 `(:wand fear :charges ,(wand-charges)))
-                  (5 `(:wand fireballs :charges ,(wand-charges)))
-                  (4 `(:wand illusion :charges ,(wand-charges)))
-                  (5 `(:wand lightning-bolts :charges ,(wand-charges)))
-                  (5 `(:wand magic-detection :charges ,(wand-charges)))
-                  (5 `(:wand metal-detection :charges ,(wand-charges)))
-                  (4 `(:wand negation :charges ,(wand-charges)))
-                  (5 `(:wand paralysation :charges ,(wand-charges)))
-                  (5 `(:wand polymorphing :charges ,(wand-charges)))
-                  (4 `(:wand secret-door-detection
-                       :charges ,(wand-charges)))
-                  (4 `(:wand trap-detection :charges ,(wand-charges)))
-                  (1 `(:staff commanding :charges nil))
-                  (2 `(:staff dispelling :charges ,(staff-charges)))
-                  (3 `(:staff druids :charges ,(staff-charges)))
-                  (3 `(:staff ,(choose 19 'air
-                                       19 'earth
-                                       19 'fire
-                                       19 'water
-                                        6 'air-and-water
-                                        6 'earth-and-fire
-                                        2 'elemental-power)
-                       :charges ,(staff-charges)))
-                  (2 `(:staff harming :charges ,(staff-charges)))
-                  (7 `(:staff healing :charges ,(staff-charges)))
-                  (1 `(:staff power :charges ,(staff-charges)))
-                  (3 `(:staff snake :charges ,(staff-charges)))
-                  (3 `(:staff striking :charges ,(staff-charges)))
-                  (2 `(:staff withering :charges ,(staff-charges)))
-                  (1 `(:staff wizardry :charges ,(staff-charges)))
-                  (2 `(:rod cancellation))
-                  (1 `(:rod dominion))
-                  (1 `(:rod health))
-                  (2 `(:rod inertia))
-                  (1 `(:rod parrying))
-                  (1 `(:rod victory))
-                  (3 `(:rod weaponry))
-                  (1 `(:rod wyrm :colour ,(choose 5 'gold
-                                                  3 'blue
-                                                  2 'black)))))
-          (ring ()
-            (pick (2 `(:ring animal-control))
-                  (6 `(:ring delusion))
-                  (1 `(:ring djinni-summoning))
-                  (4 `(:ring ear))
-                  (4 `(:ring ,(choose 19 'air-adaptation
-                                      19 'earth-adaptation
-                                      19 'fire-adaptation
-                                      19 'water-adaptation
-                                       6 'air-and-water-adaptation
-                                       6 'earth-and-fire-adaptation
-                                       2 'elemental-adaptation)))
-                  (6 `(:ring fire-resistance))
-                  (3 `(:ring holiness))
-                  (1 `(:ring human-control))
-                  (5 `(:ring invisibility))
-                  (3 `(:ring life-protection :charges ,(d 6)))
-                  (3 `(:ring memory))
-                  (2 `(:ring plant-control))
-                  (1 `(:ring protection :bonus 1 :radius 5))
-                  (10 `(:ring protection :bonus ,(choose 4 1
-                                                         3 2
-                                                         2 3
-                                                         1 4)))
-                  (4 `(:ring quickness))
-                  (1 `(:ring regeneration))
-                  (3 `(:ring remedies))
-                  (2 `(:ring safety :charges ,(d 4)))
-                  (3 `(:ring seeing))
-                  (3 `(:ring spell-eating))
-                  (2 (let* ((caster (spell-caster-type))
-                            (spells (loop repeat (d 6)
-                                          collect (random-spell caster))))
-                       `(:ring spell-storing
-                         :caster ,caster
-                         :spells ,(remove-duplicates (sort spells
-                                                           #'string<)))))
-                  (2 `(:ring spell-turning))
-                  (4 `(:ring survival :charges ,(+ 100 (d 100))))
-                  (2 `(:ring telekinesis))
-                  (4 `(:ring truth))
-                  (3 `(:ring truthfulness))
-                  (2 `(:ring truthlessness))
-                  (5 `(:ring water-walking))
-                  (5 `(:ring weakness))
-                  (2 `(:ring wishes :charges ,(choose 4 1
-                                                      3 2
-                                                      2 3
-                                                      1 4)))
-                  (2 `(:ring x-ray-vision))))
+            (pick-matching (form)
+              (5 (:user (:magic-user :elf))
+                 `(:wand cold :charges ,(wand-charges)))
+              (5 (:user (:magic-user :elf))
+                 `(:wand enemy-detection :charges ,(wand-charges)))
+              (4 (:user (:magic-user :elf))
+                 `(:wand fear :charges ,(wand-charges)))
+              (5 (:user (:magic-user :elf))
+                 `(:wand fireballs :charges ,(wand-charges)))
+              (4 (:user (:magic-user :elf))
+                 `(:wand illusion :charges ,(wand-charges)))
+              (5 (:user (:magic-user :elf))
+                 `(:wand lightning-bolts :charges ,(wand-charges)))
+              (5 (:user (:magic-user :elf))
+                 `(:wand magic-detection :charges ,(wand-charges)))
+              (5 (:user (:magic-user :elf))
+                 `(:wand metal-detection :charges ,(wand-charges)))
+              (4 (:user (:magic-user :elf))
+                 `(:wand negation :charges ,(wand-charges)))
+              (5 (:user (:magic-user :elf))
+                 `(:wand paralysation :charges ,(wand-charges)))
+              (5 (:user (:magic-user :elf))
+                 `(:wand polymorphing :charges ,(wand-charges)))
+              (4 (:user (:magic-user :elf))
+                 `(:wand secret-door-detection :charges ,(wand-charges)))
+              (4 (:user (:magic-user :elf))
+                 `(:wand trap-detection :charges ,(wand-charges)))
+              (1 (:user (:magic-user :elf :cleric :druid :palatin))
+                 `(:staff commanding :charges nil))
+              (2 nil
+                 `(:staff dispelling :charges ,(staff-charges)))
+              (3 (:user :druid)
+                 `(:staff druids :charges ,(staff-charges)))
+              (3 (:user (:magic-user :elf))
+                 `(:staff ,(choose 19 'air
+                                   19 'earth
+                                   19 'fire
+                                   19 'water
+                                   6 'air-and-water
+                                   6 'earth-and-fire
+                                   2 'elemental-power)
+                   :charges ,(staff-charges)))
+              (2 (:user (:cleric :druid :paladin))
+                 `(:staff harming :charges ,(staff-charges)))
+              (7 (:user (:cleric :druid :paladin))
+                 `(:staff healing :charges ,(staff-charges)))
+              (1 (:user (:cleric :druid :magic-user :elf :paladin))
+                 `(:staff power :charges ,(staff-charges)))
+              (3 (:user (:cleric :druid :paladin))
+                 `(:staff snake :charges ,(staff-charges)))
+              (3 (:user (:cleric :druid :magic-user :elf :paladin))
+                 `(:staff striking :charges ,(staff-charges)))
+              (2 (:user (:cleric :druid :paladin))
+                 `(:staff withering :charges ,(staff-charges)))
+              (1 (:user (:magic-user :elf))
+                 `(:staff wizardry :charges ,(staff-charges)))
+              (2 nil `(:rod cancellation))
+              (1 nil `(:rod dominion))
+              (1 (:user (:cleric :druid :paladin)) `(:rod health))
+              (2 (:user (:dwarf :halfling :elf :fighter
+                         :paladin :thief :mystic))
+                 `(:rod inertia))
+              (1 nil `(:rod parrying))
+              (1 nil `(:rod victory))
+              (3 (:user (:dwarf :halfling :elf :fighter
+                         :paladin :thief :mystic))
+                 `(:rod weaponry))
+              (1 nil
+                 `(:rod wyrm :colour ,(choose 5 'gold
+                                              3 'blue
+                                              2 'black)))))
+          (ring (&optional (recursivep nil))
+            (pick-matching (form)
+              (2 (:cursed nil) `(:ring animal-control))
+              ((if recursivep 0 6)
+               (:cursed t)
+               (setf form :any)
+               `(:ring delusion :fakes ,@(cdr (ring t))))
+              (1 (:cursed nil) `(:ring djinni-summoning))
+              (4 (:cursed nil) `(:ring ear))
+              (4 (:cursed nil) `(:ring ,(choose 19 'air-adaptation
+                                                19 'earth-adaptation
+                                                19 'fire-adaptation
+                                                19 'water-adaptation
+                                                6 'air-and-water-adaptation
+                                                6 'earth-and-fire-adaptation
+                                                2 'elemental-adaptation)))
+              (6 (:cursed nil) `(:ring fire-resistance))
+              (3 (:cursed nil :user (:cleric :druid :paladin))
+                 `(:ring holiness))
+              (1 (:cursed nil) `(:ring human-control))
+              (5 (:cursed nil) `(:ring invisibility))
+              (3 (:cursed nil) `(:ring life-protection :charges ,(d 6)))
+              (3 (:cursed nil
+                  :user (:cleric :druid :magic-user :elf :paladin))
+                 `(:ring memory))
+              (2 (:cursed nil) `(:ring plant-control))
+              (1 (:cursed nil) `(:ring protection :bonus 1 :radius 5))
+              (10 (:cursed nil) `(:ring protection :bonus ,(choose 4 1
+                                                                   3 2
+                                                                   2 3
+                                                                   1 4)))
+              (4 (:cursed nil) `(:ring quickness))
+              (1 (:cursed nil) `(:ring regeneration))
+              (3 (:cursed nil) `(:ring remedies))
+              (2 (:cursed nil) `(:ring safety :charges ,(d 4)))
+              (3 (:cursed nil) `(:ring seeing))
+              (3 (:cursed t) `(:ring spell-eating))
+              (2 (:cursed nil)
+                 (let* ((caster (spell-caster-type))
+                        (spells (loop repeat (d 6)
+                                      collect (random-spell caster))))
+                   `(:ring spell-storing
+                     :caster ,caster
+                     :spells ,(remove-duplicates (sort spells
+                                                       #'string<)))))
+              (2 (:cursed nil) `(:ring spell-turning))
+              (4 (:cursed nil) `(:ring survival :charges ,(+ 100 (d 100))))
+              (2 (:cursed nil) `(:ring telekinesis))
+              (4 (:cursed nil) `(:ring truth))
+              (3 (:cursed t) `(:ring truthfulness))
+              (2 (:cursed t) `(:ring truthlessness))
+              (5 (:cursed nil) `(:ring water-walking))
+              (5 (:cursed t) `(:ring weakness))
+              (2 (:cursed nil) `(:ring wishes :charges ,(choose 4 1
+                                                                3 2
+                                                                2 3
+                                                                1 4)))
+              (2 (:cursed nil) `(:ring x-ray-vision))))
           (misc-item ()
-            (pick (2 `(:amulet protection-from-crystal-balls-and-esp))
-                  (2 `(:bag devouring))
-                  (5 `(:bag holding))
-                  (3 `(:boat undersea))
-                  (2 `(:boots levitation))
-                  (3 `(:boots speed))
-                  (2 `(:boots travelling-and-leaping))
-                  (1 `(:bowl commanding-water-elementals))
-                  (1 `(:brazier commanding-fire-elementals))
-                  (2 `(:broom flying))
-                  (1 `(:censer controlling-air-elementals))
-                  (3 `(:chime time))
-                  (2 `(:crystal-ball normal))
-                  (1 `(:crystal-ball clairaudience))
-                  (1 `(:crystal-ball esp))
-                  (2 `(:cloak displacer))
-                  (1 `(:drums panic))
-                  (1 `(:bottle efreeti))
-                  (3 `(:egg ,(choose-uniformly 'rock-baboon
-                                               'giant-bat
-                                               'black-bear
-                                               'grizzly-bear
-                                               'boar
-                                               'mountain-lion
-                                               'panther
-                                               'giant-ferret
-                                               'gecko
-                                               'draco
-                                               'racer-snake
-                                               'wolf)))
-                  (2 `(:boots elven))
-                  (2 `(:cloak elven))
-                  (1 `(:carpet flying))
-                  (2 `(:gauntlets ogre-power))
-                  (2 `(:girdle giant-strength))
-                  (2 `(:helm ,(choose-uniformly 'lawful-alignment
-                                                'neutral-alignment
-                                                'chaotic-alignment)))
-                  (2 `(:helm reading))
-                  (1 `(:helm telepathy))
-                  (1 `(:helm teleportation))
-                  (1 `(:horn blasting))
-                  (2 `(:lamp hurricane))
-                  (3 `(:lamp long-burning))
-                  (2 `(:medallion esp-30-ft-range))
-                  (1 `(:medallion esp-90-ft-range))
-                  (1 `(:mirror life-trapping)) ;;; fixme include contents
-                  (3 `(:muzzle training))
-                  (2 `(:nail finger))
-                  (3 `(:nail pointing))
-                  (5 `(:ointment ,(choose-uniformly 'blessing
-                                                    'healing
-                                                    'poison
-                                                    'scarring
-                                                    'soothing
-                                                    'tanning)))
-                  (3 `(:pouch security))
-                  (3 `(:quill copying))
-                  (4 `(:rope climbing))
-                  (2 `(:scarab protection :charges ,(d 6 2)))
-                  (3 `(:slate identification))
-                  (1 `(:stone controlling-earth-elementals))
-                  (2 `(:talisman ,(choose-uniformly 'air-travel
-                                                    'earth-travel
-                                                    'fire-travel
-                                                    'water-travel
-                                                    'elemental-travel)))
-                  (3 `(:wheel floating))
-                  (1 `(:wheel fortune))
-                  (2 `(:wheel square))))
+            (pick-matching (form)
+              (2 (:cursed nil)
+                 `(:amulet protection-from-crystal-balls-and-esp))
+              (2 (:cursed t) `(:bag devouring))
+              (5 (:cursed nil) `(:bag holding))
+              (3 (:cursed nil) `(:boat undersea))
+              (2 (:cursed nil) `(:boots levitation))
+              (3 (:cursed nil) `(:boots speed))
+              (2 (:cursed nil) `(:boots travelling-and-leaping))
+              (1 (:cursed nil) `(:bowl commanding-water-elementals))
+              (1 (:cursed nil) `(:brazier commanding-fire-elementals))
+              (2 (:cursed nil) `(:broom flying))
+              (1 (:cursed nil) `(:censer controlling-air-elementals))
+              (3 (:cursed nil) `(:chime time))
+              (2 (:cursed nil :user (:magic-user :elf))
+                 `(:crystal-ball normal))
+              (1 (:cursed nil :user (:magic-user :elf))
+                 `(:crystal-ball clairaudience))
+              (1 (:cursed nil :user (:magic-user :elf))
+                 `(:crystal-ball esp))
+              (2 (:cursed nil) `(:cloak displacer))
+              (1 (:cursed nil) `(:drums panic))
+              (1 (:cursed nil) `(:bottle efreeti))
+              (3 (:cursed nil) `(:egg ,(choose-uniformly 'rock-baboon
+                                                         'giant-bat
+                                                         'black-bear
+                                                         'grizzly-bear
+                                                         'boar
+                                                         'mountain-lion
+                                                         'panther
+                                                         'giant-ferret
+                                                         'gecko
+                                                         'draco
+                                                         'racer-snake
+                                                         'wolf)))
+              (2 (:cursed nil) `(:boots elven))
+              (2 (:cursed nil) `(:cloak elven))
+              (1 (:cursed nil) `(:carpet flying))
+              (2 (:cursed nil) `(:gauntlets ogre-power))
+              (2 (:cursed nil) `(:girdle giant-strength))
+              (2 (:cursed t)
+                 `(:helm ,(choose-uniformly 'lawful-alignment
+                                            'neutral-alignment
+                                            'chaotic-alignment)))
+              (2 (:cursed nil) `(:helm reading))
+              (1 (:cursed nil) `(:helm telepathy))
+              (1 (:cursed nil) `(:helm teleportation))
+              (1 (:cursed nil) `(:horn blasting))
+              (2 (:cursed t) `(:lamp hurricane))
+              (3 (:cursed nil) `(:lamp long-burning))
+              (2 (:cursed nil) `(:medallion esp-30-ft-range))
+              (1 (:cursed nil) `(:medallion esp-90-ft-range))
+              (1 (:cursed nil) `(:mirror life-trapping))
+                                       ; fixme include contents
+              (3 (:cursed nil) `(:muzzle training))
+              (2 (:cursed nil) `(:nail finger))
+              (3 (:cursed nil) `(:nail pointing))
+              (5 nil `(:ointment ,(pick-matching (form)
+                                    (1 (:cursed nil) 'blessing)
+                                    (1 (:cursed nil) 'healing)
+                                    (1 (:cursed t) 'poison)
+                                    (1 (:cursed t) 'scarring)
+                                    (1 (:cursed nil) 'soothing)
+                                    (1 (:cursed t) 'tanning))))
+              (3 (:cursed nil) `(:pouch security))
+              (3 (:cursed nil :user (:cleric :druid :magic-user :elf))
+                 `(:quill copying))
+              (4 (:cursed nil) `(:rope climbing))
+              (2 (:cursed nil) `(:scarab protection :charges ,(d 6 2)))
+              (3 (:cursed nil :user (:cleric :druid :magic-user :elf))
+                 `(:slate identification))
+              (1 (:cursed nil) `(:stone controlling-earth-elementals))
+              (2 (:cursed nil)
+                 `(:talisman ,(choose-uniformly 'air-travel
+                                                'earth-travel
+                                                'fire-travel
+                                                'water-travel
+                                                'elemental-travel)))
+              (3 (:cursed nil) `(:wheel floating))
+              (1 (:cursed nil) `(:wheel fortune))
+              (2 (:cursed nil) `(:wheel square))))
           (weapon-bonus (class)
             (loop for bonus from 1
                   for roll = (random 100) then (- roll item)
                                 ((d) '(70 18 8 3 1)))
                   when (< roll item) return bonus))
           (armour-size ()
-            (choose 68 'human
-                    13 'dwarf
-                    10 'elf
-                     7 'halfling
-                     2 'giant))
+            (pick-matching (form)
+              (68 (:user (:cleric :fighter :paladin :druid :thief)) 'human)
+              (13 (:user :dwarf) 'dwarf)
+              (10 (:user :elf) 'elf)
+              (7 (:user :halfling) 'halfling)
+              (2 (:user nil) 'giant)))
           (armour-piece (class)
             (let* ((bonus (weapon-bonus class))
                    (power (and (percentp (* 5 (1+ bonus)))
                                      (7 `(remove-curse :charges ,(d 3))))))
                    (cursedp (if (and power (eq (car power) 'remove-curse))
                                 nil
-                                (zerop (random 8)))))
+                                (cursedp 8))))
               `(:bonus ,bonus
                 ,@(and power (cons :power power))
                 :size ,(armour-size)
                 ,@(and cursedp `(:cursed t)))))
           (armour ()
-            (pick (10 `((:leather ,@(armour-piece 'd))))
-                  ( 7 `((:scale-mail ,@(armour-piece 'd))))
-                  (13 `((:chain-mail ,@(armour-piece 'c))))
-                  ( 9 `((:banded-mail ,@(armour-piece 'd))))
-                  (11 `((:plate-mail ,@(armour-piece 'b))))
-                  ( 5 `((:suit-armour ,@(armour-piece 'b))))
-                  (20 `((:shield ,@(armour-piece 'a))))
-                  ( 2 `((:scale-mail ,@(armour-piece 'd))
-                        (:shield ,@(armour-piece 'a))))
-                  ( 8 `((:chain-mail ,@(armour-piece 'c))
-                        (:shield ,@(armour-piece 'a))))
-                  ( 5 `((:banded-mail ,@(armour-piece 'd))
-                        (:shield ,@(armour-piece 'a))))
-                  (10 `((:plate-mail ,@(armour-piece 'b))
-                        (:shield ,@(armour-piece 'a))))))
+            (pick-matching (form)
+              (10 (:user (:cleric :fighter :paladin :druid :thief
+                          :dwarf :elf :halfling))
+                  `((:armour leather ,@(armour-piece 'd))))
+              ( 7 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour scale-mail ,@(armour-piece 'd))))
+              (13 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour chain-mail ,@(armour-piece 'c))))
+              ( 9 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour banded-mail ,@(armour-piece 'd))))
+              (11 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour plate-mail ,@(armour-piece 'b))))
+              ( 5 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour suit ,@(armour-piece 'b))))
+              (20 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:shield ,@(armour-piece 'a))))
+              ( 2 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour scale-mail ,@(armour-piece 'd))
+                    (:shield ,@(armour-piece 'a))))
+              ( 8 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour chain-mail ,@(armour-piece 'c))
+                    (:shield ,@(armour-piece 'a))))
+              ( 5 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour banded-mail ,@(armour-piece 'd))
+                    (:shield ,@(armour-piece 'a))))
+              (10 (:user (:cleric :fighter :paladin :dwarf :elf :halfling))
+                  `((:armour plate-mail ,@(armour-piece 'b))
+                    (:shield ,@(armour-piece 'a))))))
           (opponent ()
             (choose 6 'bugs
                     3 'constructs
           (missile ()
             (multiple-value-bind
                 (item class)
-                (pick (37 (values :arrow 'a))
-                      (22 (values :quarrel 'a))
-                      (11 (values :sling-stone 'a))
-                      (2 (values :blowgun 'd))
-                      (8 (values :long-bow 'd))
-                      (5 (values :short-bow 'd))
-                      (2 (values :heavy-crossbow 'd))
-                      (5 (values :light-crossbow 'd))
-                      (8 (values :sling 'd)))
+                (pick-matching (form)
+                  (37 (:user (:thief :fighter :paladin :mystic
+                             :dwarf :elf :halfling))
+                      (values :arrow 'a))
+                  (22 (:user (:thief :fighter :paladin :mystic
+                                     :dwarf :elf :halfling))
+                      (values :quarrel 'a))
+                  (11 (:user (:cleric :druid :thief :fighter :paladin
+                              :mystic :dwarf :elf :halfling))
+                      (values :sling-stone 'a))
+                  (2 (:user (:thief :fighter :paladin :mystic
+                             :dwarf :elf :halfling))
+                     (values :blowgun 'd))
+                  (8 (:user (:thief :fighter :paladin :mystic
+                             :dwarf :elf :halfling))
+                     (values :long-bow 'd))
+                  (5 (:user (:thief :fighter :paladin :mystic
+                             :dwarf :elf :halfling))
+                     (values :short-bow 'd))
+                  (2 (:user (:thief :fighter :paladin :mystic
+                             :dwarf :elf :halfling))
+                     (values :heavy-crossbow 'd))
+                  (5 (:user (:thief :fighter :paladin :mystic
+                             :dwarf :elf :halfling))
+                     (values :light-crossbow 'd))
+                  (8 (:user (:cleric :druid :thief :fighter :paladin
+                             :mystic :dwarf :elf :halfling))
+                     (values :sling 'd)))
               (ecase class
                 ((a) (let* ((bonus (weapon-bonus 'a))
                             (cursedp (zerop (random 10)))
                             (talent (and (percentp (* 5 (- 7 bonus)))
-                                         (choose 4 'biting
-                                                 5 'blinking
-                                                 5 'charming
-                                                 7 'climbing
-                                                 10 'curing
-                                                 3 'disarming
-                                                 4 'dispelling
-                                                 7 'flying
-                                                 7 'lightning
-                                                 5 'penetrating
-                                                 4 'refilling
-                                                 6 'screaming
-                                                 5 'seeking
-                                                 4 'sinking
-                                                 2 'slaying
-                                                 7 'speaking
-                                                 4 'stunning
-                                                 2 'teleporting
-                                                 5 'transporting
-                                                 4 'wounding)))
+                                         (pick (4 'biting)
+                                               (5 'blinking)
+                                               (5 'charming)
+                                               (7 'climbing)
+                                               (10 'curing)
+                                               (3 'disarming)
+                                               (4 'dispelling)
+                                               (7 'flying)
+                                               (7 'lightning)
+                                               (5 'penetrating)
+                                               (4 'refilling)
+                                               (6 'screaming)
+                                               (5 'seeking)
+                                               (4 'sinking)
+                                               (2 `(slaying
+                                                    :opponent ,(opponent)))
+                                               (7 'speaking)
+                                               (4 'stunning)
+                                               (2 'teleporting)
+                                               (5 'transporting)
+                                               (4 'wounding))))
                             (number (ecase bonus
                                       ((1) (d 10 2))
                                       ((2) (d 8 2))
                          :number ,number
                          ,@(and cursedp `(:cursed t)))))
                 ((d) (let* ((bonus (weapon-bonus 'd))
-                            (cursedp (zerop (random 10)))
+                            (cursedp (cursedp 10))
                             (modifier (weapon-modifier bonus :missilep t))
                             (range (ecase (+ bonus (d 4))
                                      ((2 3 4) nil)
           (sword ()
             (multiple-value-bind
                 (type class)
-                (pick (65 (values :normal-sword 'c))
-                      (19 (values :short-sword 'c))
-                      (8 (values :two-handed-sword 'd))
-                      (8 (values :bastard-sword 'd)))
+                (pick-matching (form)
+                  (65 nil (values :normal-sword 'c))
+                  (19 nil (values :short-sword 'c))
+                  (8 (:user (:fighter :paladin :dwarf :mystic :elf))
+                     (values :two-handed-sword 'd))
+                  (8 (:user (:fighter :paladin :dwarf
+                             :mystic :elf :halfling))
+                     (values :bastard-sword 'd)))
               (let* ((bonus (weapon-bonus class))
                      (cursedp (zerop (random 10)))
                      (modifier (sword-modifier bonus))
                   ,@(and cursedp `(:cursed t))))))
           (weapon ()
             (multiple-value-bind
-                (type returnsp class)
-                (pick (7 (values :battle-axe nil 'd))
-                      (8 (values :hand-axe (choose 3 nil 1 t) 'b))
-                      (3 (values :blackjack nil 'c))
-                      (3 (values :bola (choose 2 nil 1 t) 'b))
-                      (5 (values :club nil 'c))
-                      (14 (values :dagger (choose 11 nil 3 t) 'b))
-                      (4 (values :one-handed-flail nil 'c))
-                      (2 (values :two-handed-flail nil 'd))
-                      (3 (values :halberd nil 'd))
-                      (8 (values :war-hammer nil 'c))
-                      (4 (values :javelin (choose 3 nil 1 t) 'b))
-                      (4 (values :lance nil 'd))
-                      (7 (values :mace nil 'c))
-                      (5 (values :morning-star nil 'c))
-                      (3 (values :net (choose 2 nil 1 t) 'b))
-                      (3 (values :pike nil 'd))
-                      (2 (values :pole-axe nil 'd))
-                      (12 (values :spear (choose 3 nil 1 t) 'b))
-                      (3 (values :whip nil 'c)))
+                (type returnsp intelpc class)
+                (pick-matching (form)
+                  (7 (:user (:fighter :paladin :mystic :dwarf :elf))
+                     (values :battle-axe nil 30 'd))
+                  (8 (:user (:fighter :paladin :mystic :dwarf :thief
+                             :elf :halfling))
+                     (values :hand-axe (choose 3 nil 1 t) nil 'b))
+                  (3 (:user (:fighter :paladin :mystic :dwarf :cleric
+                             :elf :halfling))
+                     (values :blackjack nil nil 'c))
+                  (3 (:user (:fighter :paladin :mystic :dwarf :cleric
+                             :thief :elf :halfling))
+                     (values :bola (choose 2 nil 1 t) nil 'b))
+                  (5 (:user (:fighter :paladin :mystic :dwarf
+                             :thief :cleric :druid :elf :halfling))
+                     (values :club nil nil 'c))
+                  (14 (:user (:fighter :paladin :mystic :dwarf :magic-user
+                              :thief :elf :halfling))
+                      (values :dagger (choose 11 nil 3 t) 50 'b))
+                  (4 (:user (:fighter :paladin :mystic :dwarf :cleric
+                             :elf :halfling :thief))
+                     (values :one-handed-flail nil nil 'c))
+                  (2 (:user (:fighter :paladin :mystic :dwarf :cleric :elf))
+                     (values :two-handed-flail nil nil 'd))
+                  (3 (:user (:fighter :paladin :mystic :dwarf :elf))
+                     (values :halberd nil 20 'd))
+                  (8 (:user (:fighter :paladin :mystic :dwarf :cleric
+                             :druid :elf :halfling :thief))
+                     (values :war-hammer nil 30 'c))
+                  (4 (:user (:fighter :paladin :mystic :dwarf :thief
+                             :elf :halfling))
+                     (values :javelin (choose 3 nil 1 t) nil 'b))
+                  (4 (:user (:fighter :paladin :mystic :dwarf :elf))
+                     (values :lance nil nil 'd))
+                  (7 (:user (:fighter :paladin :mystic :dwarf :cleric :thief
+                             :elf :halfling :druid))
+                     (values :mace nil 35 'c))
+                  (5 (:user (:fighter :paladin :mystic :dwarf :cleric :thief
+                             :elf :halfling))
+                     (values :morning-star nil nil 'c))
+                  (3 (:user (:fighter :paladin :mystic :dwarf :cleric :thief
+                             :druid :elf :halfling))
+                     (values :net (choose 2 nil 1 t) nil 'b))
+                  (3 (:user (:fighter :paladin :mystic :dwarf :elf))
+                     (values :pike nil 20 'd))
+                  (2 (:user (:fighter :paladin :mystic :dwarf :elf))
+                     (values :pole-axe nil 20 'd))
+                  (12 (:user (:fighter :paladin :mystic :dwarf :thief
+                              :elf :halfling))
+                      (values :spear (choose 3 nil 1 t) nil 'b))
+                  (4 (:user (:fighter :paladin :mystic :dwarf :thief :cleric
+                             :druid :elf :halfling :magic-user))
+                     (values :staff nil 20 'd))
+                  (3 (:user (:fighter :paladin :mystic :dwarf :thief :cleric
+                             :druid :elf :halfling))
+                     (values :whip nil nil 'c)))
               (let* ((bonus (weapon-bonus class))
-                     (cursedp (zerop (random 10)))
+                     (cursedp (cursedp 10))
                      (modifier (sword-modifier bonus))
-                     (intel (and (percentp 40)
+                     (intel (and intelpc
+                                 (percentp intelpc)
                                  (weapon-intelligence))))
                 `(,type
                   ,@(and returnsp `(:returning t))
                   ,@modifier
                   ,@intel
                   ,@(and cursedp `(:cursed t)))))))
-    (pick ((if (symbol-match-p form :potion) 25 0) (list (potion)))
-         ((if (symbol-match-p form :scroll) 12 0) (list (scroll)))
-         ((if (symbol-match-p form :wandlike) 9 0) (list (wandlike)))
-         ((if (symbol-match-p form :ring) 6 0) (list (ring)))
-         ((if (symbol-match-p form :misc) 10 0) (list (misc-item)))
-         ((if (symbol-match-p form :armour) 10 0) (armour))
-         ((if (symbol-match-p form :missile) 11 0) (list (missile)))
-         ((if (symbol-match-p form :sword) 9 0) (list (sword)))
-         ((if (symbol-match-p form :weapon) 8 0) (list (weapon))))))
+    (pick-matching (form)
+      (25 (:type :potion) (list (potion)))
+      (12 (:type :scroll) (list (scroll)))
+      (9 (:type :wandlike :cursed nil) (list (wandlike)))
+      (6 (:type :ring) (list (ring)))
+      (10 (:type :misc) (list (misc-item)))
+      (10 (:type :armour
+          :user (:cleric :druid :fighter :paladin
+                 :thief :dwarf :elf :halfling))
+         (armour))
+      (11 (:type :missile) (list (missile)))
+      (9 (:type :sword
+         :user (:fighter :paladin :mystic :thief :dwarf :elf :halfling))
+        (list (sword)))
+      (8 (:type :weapon) (list (weapon))))))
 
 (defun treasure-type (type-code)
   (labels ((common-fur-type ()
                               'mink
                               'sable))
           (special (n)
-            (tagged-bag
+            (cons
              :special
              (loop repeat n
                    collect
                              (intern (format nil "STAR-~A"
                                              (string kind))))))))
           (gems (n)
-            (tagged-bag
+            (cons
              :gems
              (loop while (plusp n)
                    for i = (min n (d 5))
                                    (2 (values 'good 4))
                                    (1 (values 'very-good 8)))
                            (setf mod-list
-                                 (append `(:size ,mod) mod-list))
+                                 (append `(:quality ,mod) mod-list))
                            (setf value (* value mult))))
                      `(:kind ,kind
                        :value ,(max 1 (round value))
                        ,@mod-list
                        ,@(and (> i 1) `(:quantity ,i))))))))
           (jewellery (n)
-            (tagged-bag
+            (cons
              :jewellery
              (loop while (plusp n)
                    for i = (min n (d 5))
                          :encumbrance ,enc
                          ,@(and (> i 1) `(:quantity ,i))))))))
           (magic (&rest forms)
-            (tagged-bag :magic
-                        (loop with list = nil
-                              for (form n) on forms by #'cddr do
-                              (loop repeat n do
-                                    (dolist (item (magic-item form))
-                                      (push item list)))
-                              finally (return list)))))
+            (cons :magic
+                  (loop with list = nil
+                        for (form n) on forms by #'cddr do
+                        (loop repeat n do
+                              (dolist (item (magic-item (list :type form)))
+                                (push item list)))
+                        finally (return list)))))
     (ecase type-code
 
       ;; treasure in lair
                (and (percentp 50) (gems (d 6 6)))
                (and (percentp 50) (jewellery (d 6 6)))
                (and (percentp 10) (special (d 2)))
-               (and (percentp 30) (magic t 3))))
+               (and (percentp 30) (magic :any 3))))
       ((b) (bag (tagged-bag :coins
                            (and (percentp 50) `(:cp ,(* 1000 (d 8))))
                            (and (percentp 25) `(:sp ,(* 1000 (d 6))))
                (and (percentp 50) (gems (d 6 6)))
                (and (percentp 50) (jewellery (d 6 6)))
                (and (percentp 5) (special (d 2)))
-               (and (percentp 10) (magic t 2))))
+               (and (percentp 10) (magic :any 2))))
       ((d) (bag (tagged-bag :coins
                            (and (percentp 10) `(:cp ,(* 1000 (d 8))))
                            (and (percentp 15) `(:sp ,(* 1000 (d 12))))
                (and (percentp 30) (gems (d 8)))
                (and (percentp 30) (jewellery (d 8)))
                (and (percentp 10) (special (d 2)))
-               (and (percentp 10) (magic t 1 :potion 1))))
+               (and (percentp 10) (magic :any 1 :potion 1))))
       ((e) (bag (tagged-bag :coins
                            (and (percentp 5) `(:cp ,(* 1000 (d 10))))
                            (and (percentp 30) `(:sp ,(* 1000 (d 12))))
                (and (percentp 10) (gems (d 10)))
                (and (percentp 10) (jewellery (d 10)))
                (and (percentp 15) (special (d 2)))
-               (and (percentp 25) (magic t 3 :scroll 1))))
+               (and (percentp 25) (magic :any 3 :scroll 1))))
       ((f) (bag (tagged-bag :coins
                            (and (percentp 30) `(:sp ,(* 1000 (d 10 2))))
                            (and (percentp 20) `(:ep ,(* 1000 (d 8))))
                (and (percentp 25) (gems (d 6 3)))
                (and (percentp 25) (jewellery (d 10)))
                (and (percentp 30) (special (d 3)))
-               (and (percentp 35) (magic t 4 :scroll 1))))
+               (and (percentp 35) (magic :any 4 :scroll 1))))
       ((h) (bag (tagged-bag :coins
                            (and (percentp 25) `(:cp ,(* 1000 (d 8 3))))
                            (and (percentp 50) `(:sp ,(* 1000 (d 100))))
                (and (percentp 50) (gems (d 100)))
                (and (percentp 50) (jewellery (* 10 (d 4))))
                (and (percentp 10) (special (d 2)))
-               (and (percentp 15) (magic t 4 :potion 1 :scroll 1))))
+               (and (percentp 15) (magic :any 4 :potion 1 :scroll 1))))
       ((i) (bag (tagged-bag :coins
                            (and (percentp 30) `(:pp ,(* 1000 (d 8)))))
                (and (percentp 50) (gems (d 6 2)))
                (and (percentp 50) (jewellery (d 6 2)))
                (and (percentp 5) (special (d 2)))
-               (and (percentp 15) (magic t 1))))
+               (and (percentp 15) (magic :any 1))))
       ((j) (bag (tagged-bag :coins
                            (and (percentp 25) `(:cp ,(* 1000 (d 4))))
                            (and (percentp 10) `(:sp ,(* 1000 (d 3)))))))
                (and (percentp 5) (gems (d 2)))
                (and (percentp 5) (gems (d 4)))
                (and (percentp 2) (special 1))
-               (and (percentp 2) (magic t 1))))
+               (and (percentp 2) (magic :any 1))))
       ((v) (bag (tagged-bag :coins
                            (and (percentp 10) `(:sp ,(d 100)))
                            (and (percentp 5) `(:ep ,(d 100)))
                (and (percentp 10) (gems (d 2)))
                (and (percentp 10) (gems (d 4)))
                (and (percentp 5) (special 1))
-               (and (percentp 5) (magic t 1))))
+               (and (percentp 5) (magic :any 1))))
 
       ;; unguarded treasures
       ((unguarded-1)
                        (and (percentp 50) `(:gp ,(* 10 (d 6)))))
            (and (percentp 5) (gems (d 6)))
            (and (percentp 2) (jewellery (d 6)))
-           (and (percentp 2) (magic t 1))))
+           (and (percentp 2) (magic :any 1))))
       ((unguarded-2 unguarded-3)
        (bag (tagged-bag :coins
                        `(:sp ,(* 100 (d 12)))
                        (and (percentp 50) `(:gp ,(* 100 (d 6)))))
            (and (percentp 10) (gems (d 6)))
            (and (percentp 5) (jewellery (d 6)))
-           (and (percentp 8) (magic t 1))))
+           (and (percentp 8) (magic :any 1))))
       ((unguarded-4 unguarded-5)
        (bag (tagged-bag :coins
                        `(:sp ,(* 1000 (d 6)))
                        `(:gp ,(* 200 (d 6))))
            (and (percentp 20) (gems (d 8)))
            (and (percentp 10) (jewellery (d 8)))
-           (and (percentp 10) (magic t 1))))
+           (and (percentp 10) (magic :any 1))))
       ((unguarded-6 unguarded-7)
        (bag (tagged-bag :coins
                        `(:sp ,(* 2000 (d 6)))
                        `(:gp ,(* 500 (d 6))))
            (and (percentp 30) (gems (d 10)))
            (and (percentp 15) (jewellery (d 10)))
-           (and (percentp 15) (magic t 1))))
+           (and (percentp 15) (magic :any 1))))
       ((unguarded-8 unguarded-9)
        (bag (tagged-bag :coins
                        `(:sp ,(* 5000 (d 6)))
                        `(:gp ,(* 1000 (d 6))))
            (and (percentp 40) (gems (d 12)))
            (and (percentp 20) (jewellery (d 12)))
-           (and (percentp 20) (magic t 1)))))))
+           (and (percentp 20) (magic :any 1)))))))
+
+(defconstant combine-treasures
+  '((:coins (t . +))
+    (t . append)))
+
+(defun combine2 (spec a b)
+  (labels ((comb (tag x y)
+            (dolist (pair spec)
+              (let ((label (car pair)))
+                (when (or (eq label t)
+                          (eq label tag))
+                  (return-from comb
+                    (let ((method (cdr pair)))
+                      (etypecase method
+                        (list (combine2 method x y))
+                        ((member +) (list (+ (car x) (car y))))
+                        ((or symbol function)
+                         (funcall method x y))))))))
+            (error "No combiner found for ~S." tag)))
+    (let ((list nil))
+      (dolist (pair a)
+       (let* ((tag (car pair))
+              (match (assoc tag b)))
+         (push (if (null match)
+                   pair
+                   (cons tag
+                         (comb tag (cdr pair) (cdr match))))
+               list)))
+      (dolist (pair b)
+       (let* ((tag (car pair))
+              (match (assoc tag a)))
+         (unless match
+           (push pair list))))
+      (nreverse list))))
+
+(defun combine (spec &rest lists)
+  (reduce (lambda (x y) (combine2 spec x y)) lists))
+
+(defun treasure (types)
+  (apply #'combine
+        combine-treasures
+        (loop for type in types
+              collect (treasure-type type))))
+
+(defun select-spells (table spells)
+  (loop for n in spells
+       for list across table
+       collect (sort (loop repeat n collect (apply #'choose-uniformly list))
+                     #'string<)))