Adding jquery-ui and new theme.

parent c74a220c
This diff is collapsed.
static/templates/license.mustache
\ No newline at end of file
dontuse:
echo "This makefile is for the convenience of the developer(s)."
echo "Use:"
echo "cabal configure"
echo "cabal build"
echo "cabal install"
configure:
cabal-dev configure --ghc-options="-Werror" --enable-library-profiling --enable-executable-profiling
build:
cabal-dev build
clean:
cabal-dev clean
run:
./dist/build/roguestar-server/roguestar-server +RTS -xc -p -s 2> ./log/stdout.log
This diff is collapsed.
......@@ -45,7 +45,7 @@ walkCreature face (x',y') creature_ref =
(standing_position standing)
case () of
() | not is_passable ->
do logDB log_travel INFO $ "Terrain not passable."
do logDB gameplay_log INFO $ "Terrain not passable."
return $ detail l
() | otherwise ->
return $ standing
......@@ -80,13 +80,13 @@ resolveClimb creature_ref direction = liftM (fromMaybe ClimbFailed) $ runMaybeT
ClimbUp -> (Upstairs,Downstairs)
ClimbDown -> (Downstairs,Upstairs)
when (terrain_type /= expected_starting_terrain) $
do lift $ logDB log_travel WARNING $ "Not standing on correct stairway."
do lift $ logDB gameplay_log WARNING $ "Not standing on correct stairway."
MaybeT $ return Nothing
lift $ logDB log_travel DEBUG $ "Stepping " ++ show direction ++ " from: " ++ show (plane_ref,pos)
lift $ logDB gameplay_log DEBUG $ "Stepping " ++ show direction ++ " from: " ++ show (plane_ref,pos)
plane_destination <- MaybeT $ case direction of
ClimbDown -> getBeneath plane_ref
ClimbUp -> liftM (fmap asParent . fromLocation) $ DB.whereIs plane_ref
lift $ logDB log_travel DEBUG $ "Stepping " ++ show direction ++ " to: " ++ show plane_destination
lift $ logDB gameplay_log DEBUG $ "Stepping " ++ show direction ++ " to: " ++ show plane_destination
pos' <- lift $ pickRandomClearSite 10 0 0 pos (== expected_landing_terrain) plane_destination
return $ ClimbGood direction creature_ref $
Standing { standing_plane = plane_destination,
......@@ -119,7 +119,7 @@ data TeleportJumpOutcome =
| TeleportJumpFailed
-- |
-- Teleport jump a creature about 7 units in the specified direction.
-- Teleport jump a creature about 5-7 units in the specified direction.
--
resolveTeleportJump :: (DBReadable db) => CreatureRef -> Facing -> db TeleportJumpOutcome
resolveTeleportJump creature_ref face = liftM (fromMaybe TeleportJumpFailed) $ runMaybeT $
......@@ -134,7 +134,7 @@ resolveTeleportJump creature_ref face = liftM (fromMaybe TeleportJumpFailed) $ r
standing_facing = face }
bad = TeleportJumpFailed
lift $ weightedPickM $ weightedSet [(jump_roll,good),
(5,bad)]
(1,bad)]
-- | Execute a resolved teleport jump.
executeTeleportJump :: TeleportJumpOutcome -> DB ()
......
......@@ -166,7 +166,7 @@ pickRandomClearSite_withTimeout :: (DBReadable db) =>
db (Maybe Position)
pickRandomClearSite_withTimeout (Just x) _ _ _ _ _ _ | x <= 0 = return Nothing
pickRandomClearSite_withTimeout timeout search_radius object_clear terrain_clear (Position (start_x,start_y)) terrainPredicate plane_ref =
do logDB log_plane DEBUG $ "Searching for clear site . . ."
do logDB gameplay_log DEBUG $ "Searching for clear site . . ."
xys <- liftM2 (\a b -> List.map Position $ zip a b)
(mapM (\x -> liftM (+start_x) $ getRandomR (-x,x)) [1..search_radius])
(mapM (\x -> liftM (+start_y) $ getRandomR (-x,x)) [1..search_radius])
......@@ -181,7 +181,7 @@ pickRandomClearSite_withTimeout timeout search_radius object_clear terrain_clear
let m_result = find (\p -> terrainIsClear p && clutterIsClear p) xys
case m_result of
Just result ->
do logDB log_plane DEBUG "Found clear site."
do logDB gameplay_log DEBUG "Found clear site."
return $ Just result
Nothing -> pickRandomClearSite_withTimeout
(fmap (subtract 1) timeout)
......
......@@ -37,10 +37,12 @@ import qualified Data.Set as Set
generateCreature :: Faction -> Species -> DB Creature
generateCreature faction species =
do r <- getRandomR (1,1000000)
return $ applyToCreature (species_traits $ speciesInfo species) $ empty_creature {
creature_species = species,
creature_faction = faction,
creature_random_id = r }
return $ applyToCreature (species_specials $ speciesInfo species) $
applyToCreature (species_traits $ speciesInfo species) $
empty_creature {
creature_species = species,
creature_faction = faction,
creature_random_id = r }
-- |
-- During DBRaceSelectionState, generates a new Creature for the player character.
......@@ -101,14 +103,14 @@ getDead parent_ref = filterRO (liftM ((<= 0) . creature_health) . getCreatureHea
deleteCreature :: CreatureRef -> DB ()
deleteCreature creature_ref =
do logDB log_creature INFO $ "deleteCreature; creature=" ++ show (toUID creature_ref)
do logDB gameplay_log INFO $ "deleteCreature; creature=" ++ show (toUID creature_ref)
planar <- liftM identityDetail $ getPlanarLocation creature_ref
dbUnsafeDeleteObject creature_ref $ const $ return planar
-- | Delete all dead creatures from the database.
sweepDead :: Reference a -> DB ()
sweepDead ref =
do logDB log_creature INFO "sweepDead; sweeping dead creatures"
do logDB gameplay_log INFO "sweepDead; sweeping dead creatures"
worst_to_best_critters <- sortByRO (liftM creature_health . getCreatureHealth) =<< getDead ref
flip mapM_ worst_to_best_critters $ \creature_ref ->
do dbPushSnapshot (KilledEvent creature_ref)
......
......@@ -81,14 +81,14 @@ import System.IO.Unsafe
import Roguestar.Lib.Logging
import Control.Monad.ST
import Data.STRef
import qualified Data.Vector.Unboxed as Vector
import qualified System.Random.MWC as MWC
import Data.Word
--import qualified Data.Vector.Unboxed as Vector
--import qualified System.Random.MWC as MWC
--import Data.Word
data DBContext s = DBContext {
db_info :: STRef s DB_BaseType,
db_rng :: STRef s RNG,
db_mwc_rng :: STRef s (MWC.GenST s) }
db_rng :: STRef s RNG {-,
db_mwc_rng :: STRef s (MWC.GenST s) -} } --mwc-rng tentatively removed from the program
data DB_BaseType = DB_BaseType { db_player_state :: PlayerState,
db_next_object_ref :: Integer,
......@@ -109,13 +109,13 @@ data DB a = DB { internalRunDB :: forall s. DBContext s -> ST s (Either DBError
runDB :: DB a -> DB_BaseType -> IO (Either DBError (a,DB_BaseType))
runDB dbAction database =
do rng <- randomIO
(seed :: Vector.Vector Word32) <- MWC.withSystemRandom . MWC.asGenIO $ \gen ->
MWC.uniformVector gen 2
--(seed :: Vector.Vector Word32) <- MWC.withSystemRandom . MWC.asGenIO $ \gen ->
-- MWC.uniformVector gen 2
return $ runST $
do mwc_rng_ref <- newSTRef =<< MWC.initialize seed
do -- mwc_rng_ref <- newSTRef =<< MWC.initialize seed
data_ref <- newSTRef database
rng_ref <- newSTRef rng
result <- internalRunDB dbAction (DBContext data_ref rng_ref mwc_rng_ref)
result <- internalRunDB dbAction (DBContext data_ref rng_ref {- mwc_rng_ref -})
database' <- readSTRef data_ref
return $ case result of
Left err -> Left err
......@@ -178,8 +178,8 @@ dbRandom rgen = DB $ \context ->
class (Monad db,MonadError DBError db,MonadReader DB_BaseType db,MonadRandom db,Applicative db) => DBReadable db where
dbSimulate :: DB a -> db a
dbPeepSnapshot :: (DBReadable db) => (forall m. DBReadable m => m a) -> db (Maybe a)
uniform :: (Int,Int) -> db Int
uniformVector :: Int -> (Int,Int) -> db (Vector.Vector Int)
-- uniform :: (Int,Int) -> db Int
-- uniformVector :: Int -> (Int,Int) -> db (Vector.Vector Int)
instance DBReadable DB where
dbSimulate = local id
......@@ -189,15 +189,17 @@ instance DBReadable DB where
Just snapshot ->
do liftM Just $ local (const snapshot) $ dbSimulate actionM
Nothing -> return Nothing
uniform range = DB $ \context ->
{- uniform range = DB $ \context ->
do gen <- readSTRef (db_mwc_rng context)
liftM Right $ MWC.uniformR range gen
uniformVector n (a,b) = DB $ \ context ->
do gen <- readSTRef (db_mwc_rng context)
liftM (Right . Vector.map ((+a) . (`mod` (b-a)))) $ MWC.uniformVector gen n
liftM (Right . Vector.map ((+a) . (`mod` (b-a)))) $ MWC.uniformVector gen n -}
logDB :: (DBReadable db) => String -> Priority -> String -> db ()
logDB l p s = return $! unsafePerformIO $ logM l p $ l ++ ": " ++ s
logDB l p s = unsafePerformIO $
do logM l p $ l ++ ": " ++ s
return $ return ()
ro :: (DBReadable db) => (forall m. DBReadable m => m a) -> db a
ro db = dbSimulate db
......@@ -426,7 +428,7 @@ dbModBuilding = dbModObjectComposable dbGetBuilding dbPutBuilding
-- | A low-level set location instruction. Merely guarantees the consistency of the location graph.
setLocation :: Location -> DB ()
setLocation loc =
do logDB log_database DEBUG $ "setting location: " ++ show loc
do logDB gameplay_log DEBUG $ "setting location: " ++ show loc
case loc of
IsWielded _ (Wielded c) -> dbUnwieldCreature c
IsSubsequent _ (Subsequent s v) -> shuntPlane (\subseq -> subsequent_via subseq == v) s
......@@ -524,7 +526,7 @@ dbAdvanceTime ref t = dbSetTimeCoordinate ref =<< (return . (advanceTime t)) =<<
dbNextTurn :: (DBReadable db,ReferenceType a) => [Reference a] -> db (Reference a)
dbNextTurn [] = error "dbNextTurn: empty list"
dbNextTurn refs =
do logDB log_database INFO $ "Determining whose turn is next among: " ++ (show $ List.map toUID refs)
do logDB gameplay_log INFO $ "Determining whose turn is next among: " ++ (show $ List.map toUID refs)
asks (\db -> fst $ minimumBy (comparing snd) $
List.map (\r -> (r,fromMaybe (error "dbNextTurn: missing time coordinate") $
Map.lookup (genericReference r) (db_time_coordinates db))) refs)
......
......@@ -14,7 +14,7 @@ import Control.Monad
roguestar_muconfig :: MuConfig
roguestar_muconfig = MuConfig {
muEscapeFunc = htmlEscape,
muTemplateFileDir = Just "static/",
muTemplateFileDir = Just "static/templates/",
muTemplateFileExt = Just ".mustache" }
mkAesonContext :: (Monad m) => Aeson.Value -> MuContext m
......
--Services
module Roguestar.Lib.Logging
(initLogging,
log_creature,
log_database,
log_plane,
log_travel,
log_turns,
log_behavior,
gameplay_log,
module System.Log.Logger)
where
......@@ -14,25 +9,15 @@ import System.Log.Logger
import System.Log.Handler.Simple
initLogging :: Priority -> IO ()
initLogging prio =
initLogging prio =
do logger <- fileHandler "log/roguestar.log" prio
updateGlobalLogger rootLoggerName $ setHandlers [logger]
log_creature :: String
log_creature = "lib.Creature"
log_database :: String
log_database = "lib.DB"
log_plane :: String
log_plane = "lib.Plane"
log_travel :: String
log_travel = "lib.Travel"
log_turns :: String
log_turns = "lib.Turns"
log_behavior :: String
log_behavior = "lib.Behavior"
updateGlobalLogger rootLoggerName (setLevel prio)
logM gameplay_log EMERGENCY "Initializing log."
logM gameplay_log WARNING "Logging warnings."
logM gameplay_log INFO "Logging informational messages."
logM gameplay_log DEBUG "Logging debug messages."
gameplay_log :: String
gameplay_log = "roguestar.gameplay"
......@@ -3,8 +3,9 @@
--Utility
-- | The Perception monad is a wrapper for roguestar's core
-- monad that reveals only as much information as a character
-- legitimately has. Thus, it is suitable for writing AI
-- routines as well as an API for the human player's client.
-- legitimately has. Thus, it is suitable for writingi "no-cheating"
-- AIs as well as an API for the human player's client.
--
module Roguestar.Lib.Perception
(DBPerception,
whoAmI,
......@@ -23,7 +24,8 @@ module Roguestar.Lib.Perception
Roguestar.Lib.Perception.whereIs,
compass,
depth,
myHealth)
myHealth,
Roguestar.Lib.Perception.isBehaviorAvailable)
where
import Control.Monad.Reader
......@@ -49,6 +51,7 @@ import Roguestar.Lib.SpeciesData
import Roguestar.Lib.CreatureData
import Roguestar.Lib.Tool
import Roguestar.Lib.ToolData
import Roguestar.Lib.Behavior as Behavior
import qualified Roguestar.Lib.DetailedTravel as DT
newtype (DBReadable db) => DBPerception db a = DBPerception { fromPerception :: (ReaderT CreatureRef db a) }
......@@ -141,6 +144,15 @@ convertToVisibleObjectRecord ref | (Just building_ref :: Maybe BuildingRef) <- c
return $ VisibleBuilding building_ref (detail location) (detail location) (detail location)
convertToVisibleObjectRecord _ | otherwise = error "convertToVisibleObjectRecord: Impossible case."
-- |
-- Takes a list of VisibleObjects and arranges them by their
-- position in sorted order.
--
-- The sort order should put the most "important" object
-- on top. For example, if a creature and a tool
-- both occupy a square, it is more important to display
-- the creature than the tool.
--
stackVisibleObjects :: [VisibleObject] -> Map Position [VisibleObject]
stackVisibleObjects = List.foldr insertVob Map.empty
where insertVob :: VisibleObject -> Map Position [VisibleObject] -> Map Position [VisibleObject]
......@@ -154,6 +166,9 @@ stackVisibleObjects = List.foldr insertVob Map.empty
<|>
return [vob]
-- |
-- Get the position of a visible object.
--
visibleObjectPosition :: VisibleObject -> MultiPosition
visibleObjectPosition (VisibleBuilding { visible_building_occupies = multi_position }) = multi_position
visibleObjectPosition vob = toMultiPosition $ visible_object_position vob
......@@ -213,13 +228,21 @@ compass =
sorted_signallers = sortBy (comparing $ Position.distanceBetweenSquared pos . multipositionOf) all_signallers
return $ maybe Here (faceAt pos . detail) $ listToMaybe sorted_signallers
-- |
-- Depth of the current plane below the surface.
--
depth :: (DBReadable db) => DBPerception db Integer
depth =
do plane <- whatPlaneAmIOn
liftDB $ planeDepth plane
myHealth :: (DBReadable db) => DBPerception db CreatureHealth
myHealth =
do creature_ref <- whoAmI
liftDB $ getCreatureHealth creature_ref
isBehaviorAvailable :: (DBReadable db) => Behavior -> DBPerception db Bool
isBehaviorAvailable b =
do creature_ref <- whoAmI
liftDB $ Behavior.isBehaviorAvailable b creature_ref
......@@ -19,7 +19,7 @@ module Roguestar.Lib.Roguestar
Roguestar.Lib.Roguestar.beginGame,
perceive,
perceiveSnapshot,
behave,
performBehavior,
Roguestar.Lib.Roguestar.facingBehavior,
Roguestar.Lib.Roguestar.hasSnapshot,
popSnapshot,
......@@ -193,15 +193,15 @@ perceive g f = peek g $
runPerception player_creature f
-- TODO: this should be moved into the Perception monad
facingBehavior :: Game -> Facing -> IO (Either DBError Behavior)
facingBehavior :: Game -> Facing -> IO (Either DBError FacingBehavior)
facingBehavior g facing = peek g $
do player_creature <- getPlayerCreature
Behavior.facingBehavior player_creature facing
behave :: Game -> Behavior -> IO (Either DBError ())
behave g b = poke g $
performBehavior :: Game -> Behavior -> IO (Either DBError ())
performBehavior g b = poke g $
do player_creature <- getPlayerCreature
dbPerformPlayerTurn b player_creature
performPlayerTurn b player_creature
hasSnapshot :: Game -> IO (Either DBError Bool)
hasSnapshot g = peek g DB.hasSnapshot
......
{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
--Mechanics
module Roguestar.Lib.Turns
(dbPerformPlayerTurn)
(performPlayerTurn)
where
import Prelude hiding (getContents)
......@@ -28,13 +28,13 @@ import Roguestar.Lib.DetailedLocation
import Control.Monad.Random
import Data.List as List
dbPerformPlayerTurn :: Behavior -> CreatureRef -> DB ()
dbPerformPlayerTurn beh creature_ref =
do logDB log_turns INFO $ "dbPerformPlayerTurn; Beginning player action: " ++ show beh
dbBehave beh creature_ref
logDB log_turns INFO $ "dbPerformPlayerTurn; Doing AI turns:"
performPlayerTurn :: Behavior -> CreatureRef -> DB ()
performPlayerTurn beh creature_ref =
do logDB gameplay_log INFO $ "performPlayerTurn; Beginning player action: " ++ show beh
executeBehavior beh creature_ref
logDB gameplay_log INFO $ "performPlayerTurn; Doing AI turns:"
dbFinishPendingAITurns
logDB log_turns INFO $ "dbPerformPlayerTurn; Finished all player and AI turns."
logDB gameplay_log INFO $ "performPlayerTurn; Finished all player and AI turns."
dbFinishPendingAITurns :: DB ()
dbFinishPendingAITurns =
......@@ -45,14 +45,14 @@ dbFinishPendingAITurns =
dbFinishPlanarAITurns :: PlaneRef -> DB ()
dbFinishPlanarAITurns plane_ref =
do logDB log_turns INFO $ "Running turns for plane: id=" ++ show (toUID plane_ref)
do logDB gameplay_log INFO $ "Running turns for plane: id=" ++ show (toUID plane_ref)
sweepDead plane_ref
(all_creatures_on_plane :: [CreatureRef]) <- liftM asChildren $ getContents plane_ref
any_players_left <- liftM (any (== Player)) $ mapM getCreatureFaction all_creatures_on_plane
next_turn <- dbNextTurn $ List.map genericReference all_creatures_on_plane ++ [genericReference plane_ref]
case next_turn of
_ | not any_players_left ->
do logDB log_turns INFO $ "dbFinishPlanarAITurns; Game over condition detected"
do logDB gameplay_log INFO $ "dbFinishPlanarAITurns; Game over condition detected"
setPlayerState $ GameOver PlayerIsDead
return ()
ref | ref =:= plane_ref ->
......@@ -67,15 +67,15 @@ dbFinishPlanarAITurns plane_ref =
return ()
_ -> error "dbFinishPlanarAITurns: impossible case"
planar_turn_frequency :: Integer
planar_turn_frequency = 100
planar_turn_interval :: Rational
planar_turn_interval = 1%2
monster_spawns :: [(Terrain,Species)]
monster_spawns = [(RecreantFactory,RedRecreant)]
dbPerform1PlanarAITurn :: PlaneRef -> DB ()
dbPerform1PlanarAITurn plane_ref =
do logDB log_turns INFO $ "dbPerform1PlanarAITurn; Beginning planar AI turn (for the plane itself):"
do logDB gameplay_log INFO $ "dbPerform1PlanarAITurn; Beginning planar AI turn (for the plane itself):"
(creature_locations :: [DetailedLocation (Child Creature)]) <- liftM mapLocations $ getContents plane_ref
player_locations <- filterRO (liftM (== Player) . getCreatureFaction . asChild . detail) creature_locations
num_npcs <- liftM length $ filterRO (liftM (/= Player) . getCreatureFaction . asChild . detail) creature_locations
......@@ -83,15 +83,14 @@ dbPerform1PlanarAITurn plane_ref =
do (terrain_type,species) <- weightedPickM $ unweightedSet monster_spawns
_ <- spawnNPC terrain_type species plane_ref $ List.map detail $ player_locations
return ()
dbAdvanceTime plane_ref (1%planar_turn_frequency)
dbAdvanceTime plane_ref planar_turn_interval
-- |
-- Spawn a non-player creature on the specified terrain type (or fail if not finding that terrain type)
-- and of the specified species, on the specified plane, near one of the specified positions
--
spawnNPC :: Terrain -> Species -> PlaneRef -> [Position] -> DB Bool
spawnNPC terrain_type species plane_ref player_locations =
spawnNPC terrain_type species plane_ref player_locations =
do logDB gameplay_log INFO $ "spawnNPC; Spawning an NPC"
p <- weightedPickM $ unweightedSet player_locations
m_spawn_position <- pickRandomClearSite_withTimeout (Just 2) 7 0 0 p (== terrain_type) plane_ref
case m_spawn_position of
......@@ -103,8 +102,8 @@ spawnNPC terrain_type species plane_ref player_locations =
dbPerform1CreatureAITurn :: CreatureRef -> DB ()
dbPerform1CreatureAITurn creature_ref =
do logDB log_turns INFO $ "dbPerform1CreatureAITurn; Performing a creature's AI turn: id=" ++ show (toUID creature_ref)
liftM (const ()) $ atomic (flip dbBehave creature_ref) $ P.runPerception creature_ref $ liftM (fromMaybe Vanish) $ runMaybeT $
do logDB gameplay_log INFO $ "dbPerform1CreatureAITurn; Performing a creature's AI turn: id=" ++ show (toUID creature_ref)
liftM (const ()) $ atomic (flip executeBehavior creature_ref) $ P.runPerception creature_ref $ liftM (fromMaybe Vanish) $ runMaybeT $
do let isPlayer :: forall db. (DBReadable db) => Reference () -> P.DBPerception db Bool
isPlayer ref | (Just might_be_the_player_creature_ref) <- coerceReference ref =
do f <- P.getCreatureFaction might_be_the_player_creature_ref
......@@ -119,8 +118,8 @@ dbPerform1CreatureAITurn creature_ref =
let face_to_player = faceAt my_position player_position
return $ case distanceBetweenChessboard my_position player_position of
_ | rand_x < 5 -> Wait -- if AI gets stuck, this will make sure they waste time so the game doesn't hang
_ | rand_x < 20 -> Step rand_face
1 -> Attack face_to_player
_ | rand_x < 20 -> FacingBehavior Step rand_face
1 -> FacingBehavior Attack face_to_player
-- x | x >= 10 -> Jump face_to_player -- disable this until we can handle non-player teleporting sanely
_ -> Step face_to_player
_ -> FacingBehavior Step face_to_player
......@@ -30,6 +30,7 @@ import Roguestar.Lib.Facing
import Roguestar.Lib.Logging
import Roguestar.Lib.UnitTests
import Roguestar.Lib.HTML.Mustache
import Roguestar.Lib.Behavior as Behavior
import qualified System.UUID.V4 as V4
import GHC.Stats
import Data.Aeson as Aeson
......@@ -47,17 +48,12 @@ appInit = makeSnaplet "roguestar-server-snaplet" "Roguestar Server" Nothing $
addRoutes [("/start", start),
("/play", play),
("/static", static),
("/hidden", handle404),
("/fail", handle500 (do error "my brain exploded")),
("/help-actions", staticTemplate "static/help-actions.mustache"),
("/help-map", staticTemplate "static/help-map.mustache"),
("/help", staticTemplate "static/help.mustache"),
("/participate", staticTemplate "static/participate.mustache"),
("/feedback", postFeedback <|> staticTemplate "static/feedback.mustache"),
("/feedback-thanks", staticTemplate "static/feedback-thanks.mustache"),
("/fail404", handle404),
("/fail500", handle500 (do error "my brain exploded")),
("/feedback", postFeedback),
("/feedback-thanks", staticTemplate "static/templates/feedback-thanks.mustache"),
("/options", options),
("/version-history", staticTemplate "static/version-history.mustache"),
("", staticTemplate "static/index.mustache")]
("", staticTemplate "static/templates/index.mustache")]
config <- liftIO $ getConfiguration default_timeout
game <- liftIO $ createGameState config
wrapSite (<|> handle404)
......@@ -91,10 +87,14 @@ getBasicState = gets _globals
handle404 :: Handler App App ()
handle404 =
do modifyResponse $ setResponseCode 404
staticTemplate "static/404.mustache"
staticTemplate "static/templates/404.mustache"
static_directory_config :: DirectoryConfig (Handler App App)
static_directory_config = fancyDirectoryConfig