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.11.0.4
+version:             0.12.0.0
 
 -- A short (one-line) description of the package.
 synopsis:            An engine for text-based dungeons.
@@ -57,7 +57,7 @@
   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.3 && <0.4, 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
+  build-depends:       base >=4.6 && <4.7, chatty >=0.5.1 && <0.6, 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
diff --git a/src/Game/Antisplice/Monad.hs b/src/Game/Antisplice/Monad.hs
--- a/src/Game/Antisplice/Monad.hs
+++ b/src/Game/Antisplice/Monad.hs
@@ -32,19 +32,35 @@
 import Game.Antisplice.Monad.Dungeon
 import Game.Antisplice.Monad.Vocab
 import Game.Antisplice.Utils.Atoms
+import Game.Antisplice.Utils.BST
 import Game.Antisplice.Errors
+import Control.Monad
 import Control.Monad.Trans.Class
 import Text.Chatty.Expansion
 import Text.Chatty.Expansion.Vars
 import Text.Chatty.Templates
 import Text.Chatty.Interactor.Templates
+import Text.Chatty.Channel.Printer
+import Text.Chatty.Channel.Broadcast
+import Text.Chatty.Extended.HTML
+import Text.Chatty.Extended.ANSI
 --import Language.Haskell.TH
+import System.IO
 
-mkInteractor ''SplErrT mkChatty mkRoom mkDungeon mkObject mkCounter mkPlayer mkIO mkVocab mkAtoms
-mkInteractor ''DungeonT mkPrinter mkScanner mkFinalizer mkClock mkExpanderEnv mkHistoryEnv mkRandom (mkFail ''SplErr) mkCounter mkIO  mkVocab mkAtoms
-mkInteractor ''RoomT mkChatty (mkFail ''SplErr) mkCounter mkIO mkVocab mkAtoms
-mkInteractor ''ObjectT mkChatty (mkFail ''SplErr) mkCounter mkIO mkVocab mkAtoms
-mkInteractor ''CounterT mkChatty (mkFail ''SplErr) mkRoom mkObject mkDungeon mkPlayer mkIO mkVocab mkAtoms
-mkInteractor ''PlayerT mkChatty (mkFail ''SplErr) mkCounter mkIO mkVocab mkAtoms
-mkInteractor ''VocabT mkChatty (mkFail ''SplErr) mkCounter mkDungeon mkRoom mkAtoms mkPlayer
-mkInteractor ''AtomStoreT mkChatty (mkFail ''SplErr) mkDungeon mkRoom mkVocab mkPlayer
+mkInteractor ''SplErrT mkChatty mkRoom mkDungeon mkObject mkCounter mkPlayer mkIO mkVocab mkAtoms (mkChannelPrinter ''PlayerId)
+mkInteractor ''DungeonT mkPrinter mkDefCP mkExtendedPrinter mkScanner mkFinalizer mkClock mkExpanderEnv mkHistoryEnv mkRandom (mkFail ''SplErr) mkCounter mkIO  mkVocab mkAtoms (mkChannelPrinter ''PlayerId)
+mkInteractor ''RoomT mkChatty (mkFail ''SplErr) mkCounter mkIO mkVocab mkAtoms (mkChannelPrinter ''PlayerId)
+mkInteractor ''ObjectT mkChatty (mkFail ''SplErr) mkCounter mkIO mkVocab mkAtoms (mkChannelPrinter ''PlayerId)
+mkInteractor ''CounterT mkChatty (mkFail ''SplErr) mkRoom mkObject mkDungeon mkPlayer mkIO mkVocab mkAtoms (mkChannelPrinter ''PlayerId)
+mkInteractor ''PlayerT mkChatty (mkFail ''SplErr) mkCounter mkIO mkVocab mkAtoms (mkChannelPrinter ''PlayerId)
+mkInteractor ''VocabT mkChatty (mkFail ''SplErr) mkCounter mkDungeon mkRoom mkAtoms mkPlayer (mkChannelPrinter ''PlayerId)
+mkInteractor ''AtomStoreT mkChatty (mkFail ''SplErr) mkDungeon mkRoom mkVocab mkPlayer (mkChannelPrinter ''PlayerId)
+mkInteractor ''PlayerFilterT mkArchiver mkVocab mkAtoms mkCounter
+liftM concat $ forM [''AnsiPrinterT,''HtmlPrinterT] $ mkChannelPrinter ''PlayerId
+
+instance ChannelPrinter PlayerId m => Broadcaster PlayerId (DungeonT m) where
+  bprint c str = do
+    s <- getDungeonState
+    forM_ (anyBstInorder $ playersOf s) $ \p -> do
+      b <- c $ indexOf p
+      when b $ cprint (indexOf p) str
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
@@ -78,6 +78,8 @@
 import Text.Chatty.Expansion.Vars
 import System.Chatty.Misc
 import Text.Chatty.Extended.Printer
+import Text.Chatty.Channel.Printer
+import Text.Chatty.Channel.Broadcast
 import Game.Antisplice.Utils.Graph
 import Game.Antisplice.Utils.BST
 import Game.Antisplice.Utils.AVL
@@ -100,7 +102,7 @@
 -- | Matches any 'MonadDungeon' context
 type DungeonM a = forall m.(MonadDungeon m) => m a
 -- | Matches any 'MonadDungeon' context that also implements most of the Chatty classes and some of our utility classes.
-type ChattyDungeonM a = forall m.(Functor m,ExtendedPrinter m,MonadExpand m,ExpanderEnv m,MonadDungeon m,MonadError SplErr m,MonadAtoms m,MonadClock m,MonadVocab m,MonadRandom m) => m a
+type ChattyDungeonM a = forall m.(Functor m,ExtendedPrinter m,MonadExpand m,ExpanderEnv m,MonadDungeon m,MonadError SplErr m,MonadAtoms m,MonadClock m,MonadVocab m,MonadRandom m,Broadcaster PlayerId m) => m a
 -- | The common type of all event handlers.
 type Handler = ChattyDungeonM ()
 -- | A boxed 'Handler' to avoid ImpredicativeTypes
@@ -444,6 +446,13 @@
   getDungeonState :: m DungeonState
   -- | Put the dungeon state.
   putDungeonState :: DungeonState -> m ()
+  -- | Lower a given DungeonT function
+  lowerDungeon :: DungeonT m a -> m a
+  lowerDungeon m = do
+    s <- getDungeonState
+    (a,s') <- runDungeonT m s
+    putDungeonState s'
+    return a
 
 instance Monad m => MonadDungeon (DungeonT m) where
   getDungeonState = Dungeon $ \s -> return (s,s)
diff --git a/src/Game/Antisplice/SingleUser.hs b/src/Game/Antisplice/SingleUser.hs
--- a/src/Game/Antisplice/SingleUser.hs
+++ b/src/Game/Antisplice/SingleUser.hs
@@ -29,6 +29,7 @@
 import Text.Chatty.Expansion.History
 import Text.Chatty.Extended.Printer
 import Text.Chatty.Extended.ANSI
+import Text.Chatty.Channel.Printer
 import Game.Antisplice.Monad.Dungeon
 import Game.Antisplice.Monad.Vocab
 import Game.Antisplice.Utils.Fail
@@ -45,14 +46,15 @@
 import Control.Monad
 import Control.Monad.IO.Class
 
-singleUser :: DungeonT (FailT SplErr (VocabT (AtomStoreT (CounterT (AnsiPrinterT (HistoryT (ExpanderT (NullExpanderT (HandleCloserT IO))))))))) () -> IO ()
+singleUser :: DungeonT (FailT SplErr (VocabT (AtomStoreT (CounterT (AnsiPrinterT (JoinerT (HistoryT (ExpanderT (NullExpanderT (HandleCloserT IO)))))))))) () -> IO ()
 singleUser init =
   void $
   withLazyIO $
   withExpansion $
   localEnvironment $
   flip runHistoryT [] $
-  flip runAnsiPrinterT (Dull White) $
+  runJoinerT $
+  flip runAnsiPrinterT [] $
   withCounter $
   flip runAtomStoreT none $
   flip runVocabT defVocab $
diff --git a/src/Game/Antisplice/Stereos.hs b/src/Game/Antisplice/Stereos.hs
--- a/src/Game/Antisplice/Stereos.hs
+++ b/src/Game/Antisplice/Stereos.hs
@@ -60,6 +60,7 @@
 import Game.Antisplice.Utils.Atoms
 import Game.Antisplice.Utils.None
 import Text.Chatty.Interactor.Templates
+import System.IO
 
 -- | Register the given stereotype and return its atom.
 registerStereo :: MonadAtoms m => PlayerStereo -> m (Atom PlayerStereo)
@@ -131,7 +132,7 @@
   (_,s) <- runStereoBuilderT m none
   registerStereo s
 
-mkInteractor ''StereoBuilderT mkChatty (mkFail ''SplErr) mkDungeon mkRoom mkVocab mkCounter mkAtoms mkPlayer mkIO mkObject
+mkInteractor ''StereoBuilderT mkChatty (mkFail ''SplErr) mkDungeon mkRoom mkVocab mkCounter mkAtoms mkPlayer mkIO mkObject (mkChannelPrinter ''PlayerId)
 
 -- | Default stereotype.
 defaultStereo = statsStereo $
diff --git a/src/Game/Antisplice/Templates.hs b/src/Game/Antisplice/Templates.hs
--- a/src/Game/Antisplice/Templates.hs
+++ b/src/Game/Antisplice/Templates.hs
@@ -26,6 +26,8 @@
 
 import Language.Haskell.TH
 import Text.Chatty.Templates
+import Text.Chatty.Channel.Printer
+import Text.Chatty.Channel.Broadcast
 import Game.Antisplice.Utils.Fail
 import Game.Antisplice.Utils.Run
 import Game.Antisplice.Utils.Counter
@@ -37,6 +39,8 @@
 import Control.Monad.State.Class
 import Control.Monad.IO.Class
 
+type PlayerFilterT = FilterT PlayerId
+
 -- | Automatically derive an instance for 'MonadRoom'
 mkRoom s = [d|
     instance MonadRoom m => MonadRoom ($sx m) where
@@ -59,6 +63,8 @@
     instance MonadDungeon m => MonadDungeon ($sx m) where
       getDungeonState = lift getDungeonState
       putDungeonState = lift . putDungeonState
+    instance (MonadDungeon m,ChannelPrinter PlayerId m) => Broadcaster PlayerId ($sx m) where
+      bprint c = lowerDungeon . bprint (lift . c)
   |]
   where sx = strToType s
 
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
@@ -31,6 +31,7 @@
 import Text.Chatty.Expansion.Vars
 import Text.Chatty.Expansion.History
 import Text.Chatty.Extended.Printer
+import Text.Chatty.Channel.Broadcast
 import System.Chatty.Misc
 import Game.Antisplice.Monad.Dungeon
 import Game.Antisplice.Monad.Vocab
@@ -40,6 +41,7 @@
 import Game.Antisplice.Utils.Fail
 import Game.Antisplice.Utils.Counter
 import Game.Antisplice.Utils.AVL
+import Game.Antisplice.Utils.BST
 import Game.Antisplice.Utils.Atoms
 import Control.Monad
 import Control.Concurrent
@@ -51,7 +53,7 @@
 import System.Console.Haskeline.History
 
 -- | Read Eval Print Loop for Antisplice.
-repl :: (ExtendedPrinter m,MonadScanner m,MonadError SplErr m,MonadIO m,MonadExpand m,ExpanderEnv m,MonadDungeon m,MonadAtoms m,MonadRoom m,MonadClock m,MonadVocab m,HistoryEnv m,MonadRandom m) => m ()
+repl :: (ExtendedPrinter m,MonadScanner m,MonadError SplErr m,MonadIO m,MonadExpand m,ExpanderEnv m,MonadDungeon m,MonadAtoms m,MonadRoom m,MonadClock m,MonadVocab m,HistoryEnv m,MonadRandom m,Broadcaster PlayerId m) => m ()
 repl = do
   mv <- liftIO (newEmptyMVar :: IO (MVar (Maybe String)))
   cont <- liftIO (newEmptyMVar :: IO (MVar String))
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
@@ -35,6 +35,7 @@
   anyBstInsert = avlInsert
   anyBstRemove = avlRemove
   anyBstHead = avlHead
+  anyBstInorder = avlInorder
 
 instance None (AVL a) where
   none = EmptyAVL
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
@@ -75,6 +75,8 @@
   anyBstEmpty :: t i
   -- | The root of the tree
   anyBstHead :: t i -> Maybe i
+  -- | Traverse the tree in order
+  anyBstInorder :: t i -> [i]
 
 instance Indexable i o v => AnyBST BST i o v where
   anyBstInsert = bstInsert
@@ -84,6 +86,7 @@
   anyBstLookup = bstLookup
   anyBstEmpty = EmptyBST
   anyBstHead = bstHead
+  anyBstInorder = bstInorder
 
 instance None (BST a) where
   none = EmptyBST
@@ -133,3 +136,8 @@
 bstHead :: Indexable i o v => BST i -> Maybe i
 bstHead EmptyBST = Nothing
 bstHead (BST a _ _) = Just a
+
+-- | Traverse the tree in order
+bstInorder :: Indexable i o v => BST i -> [i]
+bstInorder EmptyBST = []
+bstInorder (BST a l r) = bstInorder l ++ [a] ++ bstInorder r
diff --git a/src/Game/Antisplice/Utils/Focus.hs b/src/Game/Antisplice/Utils/Focus.hs
--- a/src/Game/Antisplice/Utils/Focus.hs
+++ b/src/Game/Antisplice/Utils/Focus.hs
@@ -41,6 +41,7 @@
   anyBstLookup o t = bstLookup o $ runFocus t
   anyBstHead t = bstHead $ runFocus t
   anyBstEmpty = Focus none
+  anyBstInorder t = bstInorder $ runFocus t
 
 focusSelect' :: Indexable i o v => o -> BST i -> Maybe (BST i)
 focusSelect' _ EmptyBST = Nothing
