diff --git a/antisplice.cabal b/antisplice.cabal
--- a/antisplice.cabal
+++ b/antisplice.cabal
@@ -10,7 +10,7 @@
 -- PVP summary:      +-+------- breaking API changes
 --                   | | +----- non-breaking API additions
 --                   | | | +--- code changes with no API change
-version:             0.7.2.0
+version:             0.8.0.1
 
 -- A short (one-line) description of the package.
 synopsis:            An engine for text-based dungeons.
@@ -48,7 +48,7 @@
 
 library
   -- Modules exported by the library.
-  exposed-modules:     Game.Antisplice.Prototypes, Game.Antisplice.Templates, Game.Antisplice.Lang, Game.Antisplice.Monad, Game.Antisplice.Rooms, Game.Antisplice.Errors, Game.Antisplice.Utils.TST, Game.Antisplice.Utils.AVL, Game.Antisplice.Utils.Run, Game.Antisplice.Utils.BST, Game.Antisplice.Utils.Graph, Game.Antisplice.Utils.Counter, Game.Antisplice.Utils.Fail, Game.Antisplice.Monad.Dungeon, Game.Antisplice.Terminal.Repl, Game.Antisplice.Events, Game.Antisplice.Monad.Vocab, Game.Antisplice.Utils.Atoms, Game.Antisplice.Stats
+  exposed-modules:     Game.Antisplice.Prototypes, Game.Antisplice.Templates, Game.Antisplice.Lang, Game.Antisplice.Monad, Game.Antisplice.Rooms, Game.Antisplice.Errors, Game.Antisplice.Utils.TST, Game.Antisplice.Utils.AVL, Game.Antisplice.Utils.Run, Game.Antisplice.Utils.BST, Game.Antisplice.Utils.Graph, Game.Antisplice.Utils.Counter, Game.Antisplice.Utils.Fail, Game.Antisplice.Monad.Dungeon, Game.Antisplice.Terminal.Repl, Game.Antisplice.Events, Game.Antisplice.Monad.Vocab, Game.Antisplice.Utils.Atoms, Game.Antisplice.Stats, Game.Antisplice.Utils.None, Game.Antisplice.Utils.Focus, Game.Antisplice.SingleUser, Game.Antisplice
   
   -- Modules included in this library but not exported.
   -- other-modules:       
@@ -57,10 +57,11 @@
   other-extensions:    TemplateHaskell, QuasiQuotes, FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances, FlexibleContexts, UndecidableInstances, RankNTypes, TypeFamilies, FunctionalDependencies
   
   -- Other library packages from which modules are imported.
-  build-depends:       base >=4.6 && <4.7, chatty >=0.2 && <0.3, text >=0.11 && <0.12, template-haskell >=2.8 && <2.9, mtl >=2.1 && <2.2, transformers >=0.3 && <0.4, haskeline >= 0.7 && < 0.8, time >= 1.4 && < 1.5, comonad >=4.0 && <4.1
+  build-depends:       base >=4.6 && <4.7, chatty >=0.2 && <0.3, text >=0.11 && <0.12, template-haskell >=2.8 && <2.9, mtl >=2.1 && <2.2, transformers >=0.3 && <0.4, haskeline >= 0.7 && < 0.8, time >= 1.4 && < 1.5
   
   -- Directories containing source files.
   hs-source-dirs:      src
   
   -- Base language which the package is written in.
   default-language:    Haskell2010
+
diff --git a/src/Game/Antisplice.hs b/src/Game/Antisplice.hs
new file mode 100644
--- /dev/null
+++ b/src/Game/Antisplice.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE RankNTypes, FlexibleContexts #-}
+
+{-
+  This module is part of Antisplice.
+  Copyleft (c) 2014 Marvin Cohrs
+
+  All wrongs reversed. Sharing is an act of love, not crime.
+  Please share Antisplice with everyone you like.
+
+  Antisplice is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Antisplice is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with Antisplice. If not, see <http://www.gnu.org/licenses/>.
+-}
+
+-- | Reexports all relevant modules
+
+module Game.Antisplice (
+  -- * Reexported modules
+  module Game.Antisplice.Monad,
+  module Game.Antisplice.Monad.Dungeon,
+  module Game.Antisplice.Monad.Vocab,
+  module Game.Antisplice.Utils.Fail,
+  module Game.Antisplice.Utils.Atoms,
+  module Game.Antisplice.Utils.None,
+  module Game.Antisplice.Utils.BST,
+  module Game.Antisplice.Utils.Counter,
+  module Game.Antisplice.Rooms,
+  module Game.Antisplice.Stats,
+  module Game.Antisplice.Prototypes,
+  module Game.Antisplice.Lang,
+  module Game.Antisplice.Errors,
+  module Game.Antisplice.Terminal.Repl,
+  module Game.Antisplice.Events,
+  -- * Type abbreviations
+  Constructor) where
+
+import Text.Chatty.Printer
+import Text.Chatty.Scanner
+import Text.Chatty.Expansion
+import Text.Chatty.Extended.Printer
+import System.Chatty.Misc
+import Game.Antisplice.Monad
+import Game.Antisplice.Monad.Dungeon
+import Game.Antisplice.Monad.Vocab
+import Game.Antisplice.Utils.Fail
+import Game.Antisplice.Utils.Atoms
+import Game.Antisplice.Utils.None
+import Game.Antisplice.Utils.BST
+import Game.Antisplice.Utils.Counter
+import Game.Antisplice.Rooms
+import Game.Antisplice.Stats
+import Game.Antisplice.Prototypes
+import Game.Antisplice.Lang
+import Game.Antisplice.Errors
+import Game.Antisplice.Terminal.Repl
+import Game.Antisplice.Events
+import Control.Monad.Error.Class
+
+-- | A dungeon constructor function
+type Constructor = forall m.(Functor m,ExtendedPrinter m,MonadExpand m,ExpanderEnv m,MonadScanner m,MonadAtoms m,MonadClock m,MonadVocab m,MonadError SplErr m) => DungeonT m ()
diff --git a/src/Game/Antisplice/Errors.hs b/src/Game/Antisplice/Errors.hs
--- a/src/Game/Antisplice/Errors.hs
+++ b/src/Game/Antisplice/Errors.hs
@@ -32,7 +32,10 @@
             | VerbMustFirstError
             | CantSeeOneError
             | DontCarryOneError
-            | WhichOneError  
+            | WhichOneError
+            | CantEquipThatError
+            | CantEquipThatThereError
+            | WhereToEquipError
             | CantWalkThereError deriving Show
 
 -- | Alias for 'FailT' 'SplErr'
diff --git a/src/Game/Antisplice/Lang.hs b/src/Game/Antisplice/Lang.hs
--- a/src/Game/Antisplice/Lang.hs
+++ b/src/Game/Antisplice/Lang.hs
@@ -27,6 +27,7 @@
 import Text.Chatty.Printer
 import Text.Chatty.Scanner
 import Text.Chatty.Expansion
+import Text.Chatty.Extended.Printer
 import Game.Antisplice.Errors
 import Game.Antisplice.Monad
 import Game.Antisplice.Monad.Dungeon
@@ -34,6 +35,8 @@
 import Game.Antisplice.Utils.Graph
 import Game.Antisplice.Utils.AVL
 import Game.Antisplice.Utils.TST
+import Game.Antisplice.Utils.None
+import Game.Antisplice.Utils.ListBuilder
 import Game.Antisplice.Rooms
 import Game.Antisplice.Stats
 import Control.Monad.Error
@@ -41,59 +44,76 @@
 import Data.Text (unpack)
 
 aliases :: [(String,String)]
-aliases =
-  ("l","look"):
-  ("n","go north"):
-  ("ne","go northeast"):
-  ("e","go east"):
-  ("se","go southeast"):
-  ("s","go south"):
-  ("sw","go southwest"):
-  ("w","go west"):
-  ("nw","go northwest"):
-  ("u","ascend"):
-  ("d","descend"):
-  ("q","quit"):
-  ("i","list inventory"):
-  ("ex","list exits"):
-  ("get","acquire"):
-  ("take","acquire"):
-  ("show","list"):
-  ("sco","list score"):
-  []
+aliases = strictBuild $ do
+  lit "l" "look"
+  lit "n" "go north"
+  lit "ne" "go northeast"
+  lit "e"  "go east"
+  lit "se" "go southeast"
+  lit "s" "go south"
+  lit "sw" "go southwest"
+  lit "w" "go west"
+  lit "nw" "go northwest"
+  lit "u" "ascend"
+  lit "d" "descend"
+  lit "q" "quit"
+  lit "i" "list inventory"
+  lit "ex" "list exits"
+  lit "get" "acquire"
+  lit "take" "acquire"
+  lit "show" "list"
+  lit "sco" "list score"
+  lit "eq" "list equipment"
 
 defVocab :: TST Token
-defVocab = foldr (\(k,v) -> tstInsert k (v k)) EmptyTST (
-  ("look",Verb):
-  ("quit",Verb):
-  ("read",Verb):
-  ("acquire",Verb):
-  ("drop",Verb):
-  ("idiot",Noun):
-  ("first",Ordn 1):
-  ("next",Ordn 1):
-  ("primary",Ordn 1):
-  ("second",Ordn 2):
-  ("commit",Verb):
-  ("suicide",Noun):
-  ("go",Verb):
-  ("ascend",Verb):
-  ("descend",Verb):
-  ("north",Fixe):
-  ("south",Fixe):
-  ("east",Fixe):
-  ("west",Fixe):
-  ("northeast",Fixe):
-  ("northwest",Fixe):
-  ("southeast",Fixe):
-  ("southwest",Fixe):
-  ("at",Prep):
-  ("enter",Verb):
-  ("list",Verb):
-  ("exits",Fixe):
-  ("inventory",Fixe):
-  ("score",Fixe):
-  [])
+defVocab = foldr (\(k,v) -> tstInsert k (v k)) EmptyTST $ strictBuild $ do
+  lit "look" Verb
+  lit "quit" Verb
+  lit "read" Verb
+  lit "acquire" Verb
+  lit "drop" Verb
+  lit "idiot" Noun
+  lit "first" $ Ordn 1
+  lit "next" $ Ordn 1
+  lit "primary" $ Ordn 1
+  lit "second" $ Ordn 2
+  lit "third" $ Ordn 3
+  lit "commit" Verb
+  lit "suicide" Noun
+  lit "go" Verb
+  lit "ascend" Verb
+  lit "descend" Verb
+  lit "north" Fixe
+  lit "south" Fixe
+  lit "east" Fixe
+  lit "west" Fixe
+  lit "northeast" Fixe
+  lit "northwest" Fixe
+  lit "southeast" Fixe
+  lit "southwest" Fixe
+  lit "at" Prep
+  lit "enter" Verb
+  lit "list" Verb
+  lit "exits" Fixe
+  lit "inventory" Fixe
+  lit "score" Fixe
+  lit "main" Fixe
+  lit "hand" Fixe
+  lit "off" Fixe
+  lit "chest" Fixe
+  lit "feet" Fixe
+  lit "wrists" Fixe
+  lit "waist" Fixe
+  lit "head" Fixe
+  lit "legs" Fixe
+  lit "back" Fixe
+  lit "hands" Fixe
+  lit "neck" Fixe
+  lit "finger" Fixe
+  lit "left" Fixe
+  lit "right" Fixe
+  lit "equipment" Fixe
+  lit "equip" Verb
 
 replaceAliases :: [String] -> [String]
 replaceAliases [] = []
@@ -144,8 +164,11 @@
   return ()
 
 act' (Verb "quit":[]) = throwError QuitError
+act' (Verb "commit":Nounc _ _ "suicide":[]) = do
+  eprintLn (Vivid Red) "You stab yourself in the chest, finally dying."
+  throwError QuitError
 act' (Verb "look":[]) = roomTriggerOnLookOf =<< getRoomState
-act' (Verb "look":Prep "at":n@Nounc{}:[]) = getAvailableObject n >>=  objectTriggerOnLookAtOf
+act' (Verb "look":Prep "at":n@Nounc{}:[]) = getAvailableObject n >>= objectTriggerOnLookAtOf
 act' (Verb "read":n@Nounc{}:[]) = getAvailableObject n >>= objectTriggerOnReadOf
 act' (Verb "enter":n@Nounc{}:[]) = getAvailableObject n >>= objectTriggerOnEnterOf
 act' (Verb "ascend":[]) = changeRoom Up
@@ -178,6 +201,31 @@
   mprintLn $ printf "Intelligence: %5i   Spirit:  %5i" int spi
   mprintLn $ printf "Stamina:      %5i   Armor:   %5i" sta arm
   mprintLn $ printf "Attack power: %5i" akp
+act' (Verb "equip":n@Nounc{}:[]) = do
+  o <- getCarriedObject n >>= equipObject
+  case o of
+    Nothing -> noneM
+    Just o -> modifyPlayerState $ \s -> s{playerInventoryOf=avlInsert o $ playerInventoryOf s}
+act' (Verb "equip":n@Nounc{}:Prep "at":ps) = do
+  k <- case ps of
+              [Fixe "main",Fixe "hand"] -> return MainHand
+              [Fixe "off",Fixe "hand"] -> return OffHand
+              [Fixe "chest"] -> return Chest
+              [Fixe "feet"] -> return Feet
+              [Fixe "wrists"] -> return Wrists
+              [Fixe "waist"] -> return Waist
+              [Fixe "head"] -> return Head
+              [Fixe "legs"] -> return Legs
+              [Fixe "back"] -> return Back
+              [Fixe "hands"] -> return Hands
+              [Fixe "neck"] -> return Neck
+              [Fixe "right",Fixe "finger"] -> return Finger1
+              [Fixe "left",Fixe "finger"] -> return Finger2
+              _ -> throwError UnintellegibleError
+  o <- getCarriedObject n >>= equipObjectAt k
+  case o of
+    Nothing -> noneM
+    Just o -> modifyPlayerState $ \s -> s{playerInventoryOf=avlInsert o $ playerInventoryOf s}
 act' (Verb "acquire":n@Nounc{}:[]) = getSeenObject n >>= (acquireObject . objectIdOf)
 act' (Verb "drop":n@Nounc{}:[]) = getCarriedObject n >>= (dropObject . objectIdOf)
 act' _ = throwError UnintellegibleError
diff --git a/src/Game/Antisplice/Monad/Dungeon.hs b/src/Game/Antisplice/Monad/Dungeon.hs
--- a/src/Game/Antisplice/Monad/Dungeon.hs
+++ b/src/Game/Antisplice/Monad/Dungeon.hs
@@ -48,6 +48,7 @@
     Faction (..),
     Attitude (..),
     -- * Players
+    PlayerId (..),
     PlayerStereo (..),
     PlayerState (..),
     PlayerT (..),
@@ -55,6 +56,7 @@
     -- * Dungeons
     DungeonState (..),
     currentRoomOf,
+    playerOf,
     DungeonT (..),
     MonadDungeon (..)
   ) where
@@ -71,15 +73,17 @@
 import Game.Antisplice.Errors
 import Game.Antisplice.Utils.Fail
 import Game.Antisplice.Utils.Counter
+import Game.Antisplice.Utils.None
+import Game.Antisplice.Utils.Focus
 import Game.Antisplice.Monad.Vocab
 import Control.Arrow
 import Control.Monad.Error
 import Control.Monad.Trans.Class
-import Control.Comonad
 import Data.Text
 import Control.Monad
 import Data.Time.Clock
 import Data.Typeable
+import Debug.Trace
 
 -- | Matches any 'MonadDungeon' context
 type DungeonM a = forall m.(MonadDungeon m) => m a
@@ -102,6 +106,12 @@
 -- | Key for equipment slot
 data EquipKey = MainHand | OffHand | Chest | Feet | Wrists | Waist | Head | Legs | Back | Hands | Neck | Finger1 | Finger2 deriving (Ord,Eq)
 
+instance Indexable EquipKey EquipKey EquipKey where
+  type ValueOf EquipKey = EquipKey
+  type IndexOf EquipKey = EquipKey
+  indexOf = id
+  valueOf = id
+
 -- | State type for RoomT
 data RoomState = RoomState {
     roomTitleOf :: !Text,
@@ -170,9 +180,18 @@
              | Mobile  -- ^ May move around.
              deriving (Ord,Eq)
 
+instance Indexable Feature Feature Feature where
+  type IndexOf Feature = Feature
+  type ValueOf Feature = Feature
+  indexOf = id
+  valueOf = id
+
 -- | Phantom ID type for objects.
 data ObjectId = ObjectId Int | FalseObject deriving (Eq,Ord)
 
+instance None ObjectId where
+  none = FalseObject
+
 -- | State type for ObjectT
 data ObjectState = ObjectState {
     objectIdOf :: !ObjectId,
@@ -188,7 +207,8 @@
     objectCurHealthOf :: !Int,
     objectStatsOf :: AVL (StatKey,Int),
     objectRouteOf :: ![NodeId],
-    objectFeaturesOf :: AVL (Feature,()),
+    objectFeaturesOf :: AVL Feature,
+    objectWearableAtOf :: AVL EquipKey,
     objectNearImplicationsOf :: [Implication],
     objectCarryImplicationsOf :: [Implication],
     objectWearImplicationsOf :: [Implication],
@@ -247,8 +267,12 @@
     stereoCalcStatBonus :: (StatKey -> Int) -> StatKey -> Int
   } deriving Typeable
 
+-- | Phantom ID type for players
+newtype PlayerId = PlayerId Int deriving (Eq,Ord)
+
 -- | State type for PlayerT
 data PlayerState = PlayerState {
+    playerIdOf :: !PlayerId,
     playerRoomOf :: !NodeId,
     playerMaxHealthOf :: !Int,
     playerCurHealthOf :: !Int,
@@ -259,6 +283,12 @@
     playerReputationOf :: AVL (Atom Faction,Int)
   }
 
+instance Indexable PlayerState PlayerId PlayerState where
+  type IndexOf PlayerState = PlayerId
+  type ValueOf PlayerState = PlayerState
+  indexOf = playerIdOf
+  valueOf = id
+
 -- | The player monad. Used to create or modify players.
 newtype PlayerT m a = Player { runPlayerT :: PlayerState -> m (a,PlayerState) }
 
@@ -278,6 +308,11 @@
   getPlayerState :: m PlayerState
   -- | Put the player state.
   putPlayerState :: PlayerState -> m ()
+  -- | Modify the player state.
+  modifyPlayerState :: (PlayerState -> PlayerState) -> m ()
+  modifyPlayerState f = do
+    s <- getPlayerState
+    putPlayerState $ f s
 
 instance Monad m => MonadPlayer (PlayerT m) where
   getPlayerState = Player $ \s -> return (s,s)
@@ -289,13 +324,19 @@
 -- | State type for DungeonT
 data DungeonState = DungeonState {
     roomsOf :: Graph RoomState Direction,
-    playerOf :: Maybe PlayerState,
+    playersOf :: Focus PlayerState,
     timeTriggersOf :: AVL (NominalDiffTime,TriggerBox)
   }
 
+instance None DungeonState where
+  none = DungeonState none none none
+
 -- | For compatibility. Earlier versions of DungeonT had a field for that.
 currentRoomOf = fmap playerRoomOf . playerOf
 
+-- | For compatibility. Earlier versions of DungeonT had a field for that.
+playerOf = anyBstHead . playersOf
+
 -- | The dungeon monad. Everything runs inside this.
 newtype DungeonT m a = Dungeon { runDungeonT :: DungeonState -> m (a,DungeonState) }
 
@@ -318,8 +359,11 @@
 instance Monad m => MonadPlayer (DungeonT m) where
   getPlayerState = Dungeon $ \s -> case playerOf s of
     Just p -> return (p,s)
-  putPlayerState s' = Dungeon $ \s -> return ((),s{playerOf=Just s'})
-
+  putPlayerState s' = Dungeon $ \s -> return (none,s{playersOf=anyBstInsert s' $ playersOf s})
+  modifyPlayerState f = Dungeon $ \s -> case playerOf s of
+    Just p -> return ((),s{playersOf=anyBstInsert (f p) $ playersOf s})
+    Nothing -> return ((),s)
+    
 -- | Typeclass for all dungeon monads.
 class (MonadRoom m,MonadPlayer m) => MonadDungeon m where
   -- | Get the dungeon state.
diff --git a/src/Game/Antisplice/Rooms.hs b/src/Game/Antisplice/Rooms.hs
--- a/src/Game/Antisplice/Rooms.hs
+++ b/src/Game/Antisplice/Rooms.hs
@@ -26,7 +26,6 @@
     -- * Room modification
     modifyRoomState,
     getRoomDesc,
-    --setRoomDesc,
     getRoomTitle,
     setRoomTitle,
     markRoom,
@@ -71,10 +70,11 @@
     schedule,
     -- * Players
     subscribePlayer,
-    modifyPlayerState,
     setPlayerRoom,
     acquireObject,
     dropObject,
+    equipObject,
+    equipObjectAt,
     -- * Masquerades
     withRoom,
     guardVisible
@@ -88,8 +88,10 @@
 import Game.Antisplice.Monad
 import Game.Antisplice.Utils.Graph
 import Game.Antisplice.Utils.Counter
+import Game.Antisplice.Utils.BST
 import Game.Antisplice.Utils.AVL
 import Game.Antisplice.Utils.Atoms
+import Game.Antisplice.Utils.None
 import Game.Antisplice.Errors
 import Control.Monad
 import Control.Monad.Error
@@ -112,7 +114,7 @@
   s <- getRoomState
   --return (fromText $ roomDescOf s)
   let descseg (DescSeg a) = getAtom a
-      descseg _ = return []
+      descseg _ = return none
   is <- mapM descseg $ concatMap objectNearImplicationsOf $ avlInorder $ roomObjectsOf s
   return $ concat $ intersperse " " is
 
@@ -143,7 +145,7 @@
   rs0 <- getRoomState
   roomTriggerOnLeaveOf rs0
   s <- getDungeonState
-  putDungeonState s{playerOf=fmap (\p -> p{playerRoomOf=n}) $ playerOf s}
+  modifyPlayerState $ \p -> p{playerRoomOf=n}
   let marked = nodeMarked $ getNode' n $ roomsOf s
   rs <- getRoomState
   unless marked $ do
@@ -174,11 +176,11 @@
 -- | Construct a new room using the room monad.
 constructRoom :: (Monad m,MonadDungeon (t m),MonadTrans t) => RoomT m a -> t m NodeId
 constructRoom m = do
-  (_,rs) <- lift $ runRoomT m $ RoomState (pack "") EmptyAVL (return ()) (return ()) (return ()) (return ()) (return ())
+  (_,rs) <- lift $ runRoomT m $ RoomState none none noneM noneM noneM noneM noneM
   s <- getDungeonState
   let (nid,g) = addNode' rs $ roomsOf s
-      s' = if isNothing $ currentRoomOf s then s{roomsOf=g,playerOf=fmap (\p -> p{playerRoomOf=nid}) $ playerOf s} else s{roomsOf=g}
-  putDungeonState s'
+  putDungeonState s{roomsOf=g}
+  when (isNothing $ currentRoomOf s) $ modifyPlayerState $ \p -> p{playerRoomOf=nid}
   return nid
 
 -- | Run a function in the context of the given room.
@@ -218,31 +220,31 @@
 constructObject :: Monad m => ObjectT m a -> m ObjectState
 constructObject m = do
   (_,o) <- runObjectT m $ ObjectState
-       FalseObject -- id
+       none -- id
        (pack "Something") -- title
        (pack "I don't know what this is.") -- desc
-       [] [] False False False False -- names, attr, 1seen?, 1acq?, 1insp?, 1eq?
+       none none False False False False -- names, attr, 1seen?, 1acq?, 1insp?, 1eq?
        100 100 -- maxhp, curhp
-       EmptyAVL [] -- stats, route
-       EmptyAVL [] [] [] -- feat, impl (n,i,w)
-       Nothing -- faction
-       (return ()) -- on 1 sight
-       (return ()) -- on sight
-       (return ()) -- on 1 acq
-       (return ()) -- on acq
-       (return ()) -- on 1 insp
-       (return ()) -- on insp
+       none none none -- stats, route, feat
+       none none none none -- wear, impl (n,i,w)
+       none -- faction
+       noneM -- on 1 sight
+       noneM -- on sight
+       noneM -- on 1 acq
+       noneM -- on acq
+       noneM -- on 1 insp
+       noneM -- on insp
        (mprintLn "There is nothing special about this.") -- on look at
        (mprintLn "There is nothing inside this.") -- on look in
        (mprintLn "There is nothing written on this.") -- on read
        (mprintLn "You cannot enter this.") -- on enter
-       (return ()) -- on enter room
-       (return ()) -- on leave room
-       (return ()) -- on announce
-       (return ()) -- on drop
-       (return ()) -- on 1 eq
-       (return ()) -- on eq
-       (return ()) -- on uneq
+       noneM -- on enter room
+       noneM -- on leave room
+       noneM -- on announce
+       noneM -- on drop
+       noneM -- on 1 eq
+       noneM -- on eq
+       noneM -- on uneq
   return o
 
 -- | Remove an object from the current room and return its state.
@@ -304,17 +306,12 @@
 addObjectAttr t = modifyObjectState $ \s -> s{objectAttributesOf=t:objectAttributesOf s}
 
 -- | Create a new player using the player monad
-subscribePlayer :: (MonadDungeon (t m),MonadTrans t,Monad m) => PlayerT m a -> t m ()
+subscribePlayer :: (MonadDungeon (t m),MonadTrans t,MonadCounter (t m),Monad m) => PlayerT m a -> t m ()
 subscribePlayer m = do
   s <- getDungeonState
-  (_,a) <- lift $ runPlayerT m $ PlayerState (rootNode $ roomsOf s) 100 100 EmptyAVL EmptyAVL EmptyAVL [] EmptyAVL
-  putDungeonState s{playerOf=Just a}
-
--- | Modify the player state
-modifyPlayerState :: MonadPlayer m => (PlayerState -> PlayerState) -> m ()
-modifyPlayerState f = do
-  s <- getPlayerState
-  putPlayerState $ f s
+  i <- countOn
+  (_,a) <- lift $ runPlayerT m $ PlayerState (PlayerId i) (rootNode $ roomsOf s) 100 100 none none none none none
+  putDungeonState s{playersOf=anyBstInsert a $ playersOf s}
 
 -- | Move the current player to the given room, but don't trigger anything.
 setPlayerRoom :: MonadPlayer m => NodeId -> m ()
@@ -327,7 +324,7 @@
 -- | Set whether the current object is a mob.
 setObjectIsMob :: MonadObject m => Bool -> m ()
 setObjectIsMob b = modifyObjectState $ \o -> o{objectFeaturesOf=a b $ objectFeaturesOf o}
-  where a True = avlInsert (Mobile,())
+  where a True = avlInsert Mobile
         a False = avlRemove Mobile
 
 -- | Check if the current object is acquirable.
@@ -337,7 +334,7 @@
 -- | Set whether the current object is acquirable.
 setObjectIsAcquirable :: MonadObject m => Bool -> m ()
 setObjectIsAcquirable b = modifyObjectState $ \o -> o{objectFeaturesOf=a b $ objectFeaturesOf o}
-  where a True = avlInsert (Acquirable,())
+  where a True = avlInsert Acquirable
         a False = avlRemove Acquirable
 
 -- | Schedule an event for a given time offset (in milliseconds).
@@ -431,3 +428,27 @@
   o <- getAtom a
   insertRoomObject o{objectIdOf=ObjectId i}
   return $ ObjectId i
+
+-- | Equip the given object at a given key
+equipObjectAt :: (MonadPlayer m,MonadError SplErr m) => EquipKey -> ObjectState -> m (Maybe ObjectState)
+equipObjectAt k o
+  | isJust (avlLookup k $ objectWearableAtOf o) = do
+      p <- getPlayerState
+      let o1 = avlLookup k $ playerEquipOf p
+      putPlayerState p{playerEquipOf=avlInsert (k,o) $ playerEquipOf p}
+      return o1
+  | otherwise = throwError CantEquipThatThereError
+
+-- | Equip the given object somewhere
+equipObject :: (MonadPlayer m,MonadError SplErr m) => ObjectState -> m (Maybe ObjectState)
+equipObject o =
+  case avlInorder $ objectWearableAtOf o of
+    [] -> throwError CantEquipThatError
+    [k] -> equipObjectAt k o
+    ks -> do
+      p <- getPlayerState
+      case filter (isJust.flip avlLookup (playerEquipOf p)) ks of
+        [] -> throwError WhereToEquipError
+        [k] -> equipObjectAt k o
+        _ -> throwError WhereToEquipError
+    
diff --git a/src/Game/Antisplice/SingleUser.hs b/src/Game/Antisplice/SingleUser.hs
new file mode 100644
--- /dev/null
+++ b/src/Game/Antisplice/SingleUser.hs
@@ -0,0 +1,65 @@
+{-
+  This module is part of Antisplice.
+  Copyleft (c) 2014 Marvin Cohrs
+
+  All wrongs reversed. Sharing is an act of love, not crime.
+  Please share Antisplice with everyone you like.
+
+  Antisplice is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Antisplice is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with Antisplice. If not, see <http://www.gnu.org/licenses/>.
+-}
+
+module Game.Antisplice.SingleUser where
+
+import Text.Chatty.Printer
+import Text.Chatty.Finalizer
+import Text.Chatty.Interactor
+import Text.Chatty.Expansion
+import Text.Chatty.Extended.Printer
+import Text.Chatty.Extended.ANSI
+import Game.Antisplice.Monad.Dungeon
+import Game.Antisplice.Monad.Vocab
+import Game.Antisplice.Utils.Fail
+import Game.Antisplice.Utils.Counter
+import Game.Antisplice.Utils.Atoms
+import Game.Antisplice.Utils.None
+import Game.Antisplice.Errors
+import Game.Antisplice.Monad
+import Game.Antisplice.Rooms
+import Game.Antisplice.Lang
+import Game.Antisplice.Terminal.Repl
+import Text.Printf
+import System.IO
+import Control.Monad
+import Control.Monad.IO.Class
+
+singleUser :: DungeonT (FailT SplErr (VocabT (AtomStoreT (CounterT (AnsiPrinterT (ExpanderT (HandleCloserT IO))))))) () -> IO ()
+singleUser init =
+  void $
+  withLazyIO $
+  localEnvironment $
+  flip runAnsiPrinterT (Dull White) $
+  withCounter $
+  flip runAtomStoreT none $
+  flip runVocabT defVocab $
+  do
+    r <- runFailT $ flip runDungeonT none $ do
+      init
+      reenterCurrentRoom
+      roomTriggerOnAnnounceOf =<< getRoomState
+      roomTriggerOnLookOf =<< getRoomState
+      repl
+    case r :: Either SplErr ((),DungeonState) of
+      Left QuitError -> mprintLn "Bye bye!"
+      Left e -> mprintLn $ printf "Error occured: %s\n" $ show e
+      Right x -> noneM
diff --git a/src/Game/Antisplice/Terminal/Repl.hs b/src/Game/Antisplice/Terminal/Repl.hs
--- a/src/Game/Antisplice/Terminal/Repl.hs
+++ b/src/Game/Antisplice/Terminal/Repl.hs
@@ -78,6 +78,9 @@
             Left WhichOneError -> mprintLn "Which one do you mean?"
             Left CantSeeOneError -> mprintLn "I can't see one here."
             Left DontCarryOneError -> mprintLn "You don't carry one."
+            Left CantEquipThatError -> mprintLn "I can't equip that."
+            Left CantEquipThatThereError -> mprintLn "I can't wear that there. You might want to try some other place?"
+            Left WhereToEquipError -> mprintLn "Where?"
             Left e -> throwError e
           throwError DoneError
         Just Nothing -> liftIO (killThread thr) >> mprintLn "quit" >> throwError QuitError
diff --git a/src/Game/Antisplice/Utils/AVL.hs b/src/Game/Antisplice/Utils/AVL.hs
--- a/src/Game/Antisplice/Utils/AVL.hs
+++ b/src/Game/Antisplice/Utils/AVL.hs
@@ -25,6 +25,7 @@
 module Game.Antisplice.Utils.AVL (avlMax,avlMin,avlLookup,avlHeight,avlSize,avlInsert,avlRemove,AVL (EmptyAVL,AVL),avlRoot,avlPreorder,avlPostorder,avlInorder) where
 
 import Game.Antisplice.Utils.BST
+import Game.Antisplice.Utils.None
 
 instance Indexable i o v => AnyBST AVL i o v where
   anyBstMax = avlMax
@@ -33,7 +34,11 @@
   anyBstEmpty = EmptyAVL
   anyBstInsert = avlInsert
   anyBstRemove = avlRemove
+  anyBstHead = avlHead
 
+instance None (AVL a) where
+  none = EmptyAVL
+
 -- | An AVL tree.
 data AVL a = EmptyAVL | AVL a Int Int !(AVL a) !(AVL a)
 
@@ -142,6 +147,11 @@
 avlRoot :: AVL i -> i
 avlRoot EmptyAVL = error "Trying to get the root of an empty AVL tree."
 avlRoot (AVL a _ _ _ _) = a
+
+-- | Get the root of the tree (safely)
+avlHead :: AVL i -> Maybe i
+avlHead EmptyAVL = Nothing
+avlHead t = Just $ avlRoot t
 
 -- | Traverse the tree, order (head, left, right)
 avlPreorder :: AVL i -> [i]
diff --git a/src/Game/Antisplice/Utils/BST.hs b/src/Game/Antisplice/Utils/BST.hs
--- a/src/Game/Antisplice/Utils/BST.hs
+++ b/src/Game/Antisplice/Utils/BST.hs
@@ -24,6 +24,8 @@
 -- | Provides a typeclass for all binary search trees and an unbalanced implementation
 module Game.Antisplice.Utils.BST where
 
+import Game.Antisplice.Utils.None
+
 -- | Only instances of Indexable may be saved in a BST
 class Ord o => Indexable i o v | i -> o, i -> v where
   type IndexOf i
@@ -59,6 +61,8 @@
   anyBstLookup :: o -> t i -> Maybe v
   -- | An empty tree
   anyBstEmpty :: t i
+  -- | The root of the tree
+  anyBstHead :: t i -> Maybe i
 
 instance Indexable i o v => AnyBST BST i o v where
   anyBstInsert = bstInsert
@@ -67,7 +71,11 @@
   anyBstMin =  bstMin
   anyBstLookup = bstLookup
   anyBstEmpty = EmptyBST
+  anyBstHead = bstHead
 
+instance None (BST a) where
+  none = EmptyBST
+
 -- | An unbalanced binary search tree
 data BST a = EmptyBST | BST a !(BST a) !(BST a)
 
@@ -108,3 +116,8 @@
   | o == indexOf a = Just $ valueOf a
   | o < indexOf a = bstLookup o l
   | o > indexOf a = bstLookup o r
+
+-- | Return the tree's root
+bstHead :: Indexable i o v => BST i -> Maybe i
+bstHead EmptyBST = Nothing
+bstHead (BST a _ _) = Just a
diff --git a/src/Game/Antisplice/Utils/Focus.hs b/src/Game/Antisplice/Utils/Focus.hs
new file mode 100644
--- /dev/null
+++ b/src/Game/Antisplice/Utils/Focus.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies, TypeFamilies #-}
+
+{-
+  This module is part of Antisplice.
+  Copyleft (c) 2014 Marvin Cohrs
+
+  All wrongs reversed. Sharing is an act of love, not crime.
+  Please share Antisplice with everyone you like.
+
+  Antisplice is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Antisplice is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with Antisplice. If not, see <http://www.gnu.org/licenses/>.
+-}
+
+-- | Provides an unbalanced BST with a focus on the root.
+module Game.Antisplice.Utils.Focus (Focus, focusSelect) where
+
+import Game.Antisplice.Utils.BST
+import Game.Antisplice.Utils.None
+
+-- | An unbalanced BST with a focus on the root.
+newtype Focus a = Focus { runFocus :: BST a }
+
+instance None (Focus a) where
+  none = Focus none
+
+instance Indexable i o v => AnyBST Focus i o v where
+  anyBstInsert i t = Focus $ focusInsert' i $ runFocus t
+  anyBstRemove o t = Focus $ bstRemove o $ runFocus t
+  anyBstMax t = bstMax $ runFocus t
+  anyBstMin t = bstMin $ runFocus t
+  anyBstLookup o t = bstLookup o $ runFocus t
+  anyBstHead t = bstHead $ runFocus t
+  anyBstEmpty = Focus none
+
+focusSelect' :: Indexable i o v => o -> BST i -> Maybe (BST i)
+focusSelect' _ EmptyBST = Nothing
+focusSelect' o t@(BST a l r)
+  | o == indexOf a = Just t
+  | o < indexOf a = case focusSelect' o l of
+      Just (BST a' l' r') -> Just $ BST a' l' (BST a r' r)
+      Nothing -> Nothing
+  | otherwise = case focusSelect' o r of
+      Nothing -> Nothing
+      Just (BST a' l' r') -> Just $ BST a' (BST a l l') r'
+
+focusInsert' :: Indexable i o v => i -> BST i -> BST i
+focusInsert' i = unjust . focusSelect' (indexOf i) . bstInsert i
+  where unjust (Just j) = j
+
+-- | Rotate the tree such that the given element becomes the root node.
+focusSelect o t = fmap Focus $ focusSelect' o $ runFocus t
diff --git a/src/Game/Antisplice/Utils/Graph.hs b/src/Game/Antisplice/Utils/Graph.hs
--- a/src/Game/Antisplice/Utils/Graph.hs
+++ b/src/Game/Antisplice/Utils/Graph.hs
@@ -26,6 +26,7 @@
 
 import Game.Antisplice.Utils.BST
 import Game.Antisplice.Utils.AVL
+import Game.Antisplice.Utils.None
 
 -- | Phantom type for a node ID
 newtype NodeId = NodeId Int deriving (Eq,Show,Ord)
@@ -61,6 +62,9 @@
 -- | An empty graph
 emptyGraph :: Graph a b
 emptyGraph = Graph EmptyAVL [] (NodeId 0)
+
+instance None (Graph a b) where
+  none = emptyGraph
 
 -- | Add a node to the graph
 addNode :: a -> Graph a b -> Graph a b
diff --git a/src/Game/Antisplice/Utils/None.hs b/src/Game/Antisplice/Utils/None.hs
new file mode 100644
--- /dev/null
+++ b/src/Game/Antisplice/Utils/None.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE FlexibleInstances #-}
+
+{-
+  This module is part of Antisplice.
+  Copyleft (c) 2014 Marvin Cohrs
+
+  All wrongs reversed. Sharing is an act of love, not crime.
+  Please share Antisplice with everyone you like.
+
+  Antisplice is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Antisplice is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with Antisplice. If not, see <http://www.gnu.org/licenses/>.
+-}
+
+-- | Provides a typeclass for everything that may carry a void value
+module Game.Antisplice.Utils.None where
+
+import Data.Maybe
+import Data.Text (pack, Text)
+
+-- | Typeclass for everything that may carry a void value
+class None n where
+  none :: n
+
+instance None [a] where
+  none = []
+
+instance None (Maybe a) where
+  none = Nothing
+
+instance None () where
+  none = ()
+
+instance None Text where
+  none = pack ""
+
+instance None (a -> a) where
+  none = id
+
+instance Monad m => None (a -> m a) where
+  none = return
+
+-- | Wrap the void into a monad.
+noneM :: (Monad m,None n) => m n
+noneM = return none
+
+-- | Join a maybe into the underlying type. 'Nothing' becomes 'none'.
+joinMaybe :: None n => Maybe n -> n
+joinMaybe (Just j) = j
+joinMaybe Nothing = none
+
+-- | Wrap the value into a maybe. 'none' becomes 'Nothing'.
+expandMaybe :: (Eq n,None n) => n -> Maybe n
+expandMaybe n
+  | n == none = Nothing
+  | otherwise = Just n
+
+-- | Clean the maybe by pulling wrapped 'none's to the outside (as a 'Nothing').
+cleanMaybe :: (Eq n,None n) => Maybe n -> Maybe n
+cleanMaybe = expandMaybe . joinMaybe
+
+-- | Eliminate all void elements from the list.
+reduce :: (Eq n,None n) => [n] -> [n]
+reduce = filter (/=none)
+
+-- | Eliminate all 'Nothing's from the list and unjust all remaining values.
+reduceMaybe :: [Maybe a] -> [a]
+reduceMaybe = map unjust . filter isJust
+  where unjust (Just j) = j
diff --git a/src/Game/Antisplice/Utils/TST.hs b/src/Game/Antisplice/Utils/TST.hs
--- a/src/Game/Antisplice/Utils/TST.hs
+++ b/src/Game/Antisplice/Utils/TST.hs
@@ -23,6 +23,7 @@
 module Game.Antisplice.Utils.TST where
 
 import Data.Maybe
+import Game.Antisplice.Utils.None
 
 -- | A ternary search trie
 data TST a = EmptyTST | TST Char (Maybe a) (TST a) (TST a) (TST a)
@@ -49,3 +50,6 @@
 -- | Check if the TST contains the given key
 tstContains :: String -> TST a -> Bool
 tstContains cs = isJust . tstLookup cs
+
+instance None (TST a) where
+  none = EmptyTST
