packages feed

LambdaHack-0.1.20110918: src/ActorAdd.hs

module ActorAdd where

import Prelude hiding (floor)
import qualified Data.IntMap as IM
import Data.List as L
import Data.Map as M
import Data.Ratio
import Data.Maybe
import qualified Data.Char as Char

import Geometry
import State
import Level
import Dungeon
import Actor
import ActorState
import ActorKind
import Random
import qualified Config

-- Generic functions

-- setting the time of new monsters to 0 makes them able to
-- move immediately after generation; this does not seem like
-- a bad idea, but it would certainly be "more correct" to set
-- the time to the creation time instead
template :: ActorKind -> Int -> Loc -> Actor
template mk hp loc = Actor mk hp Nothing TCursor loc [] 'a' 0

nearbyFreeLoc :: Loc -> State -> Loc
nearbyFreeLoc origin state@(State { slevel = Level { lmap = map } }) =
  let hs = levelHeroList state
      ms = levelMonsterList state
      places = origin : L.nub (concatMap surroundings places)
      good loc = open (map `at` loc) && not (loc `L.elem` L.map aloc (hs ++ ms))
  in  fromMaybe (error "no nearby free location found") $ L.find good places

-- Adding heroes

findHeroName :: Config.CP -> Int -> String
findHeroName config n =
  let heroName = Config.getOption config "heroes" ("HeroName_" ++ show n)
  in  fromMaybe ("hero number " ++ show n) heroName

-- | Create a new hero on the current level, close to the given location.
addHero :: Loc -> State -> State
addHero ploc state =
  let config = sconfig state
      bHP = Config.get config "heroes" "baseHP"
      mk = hero {bhpMin = bHP, bhpMax = bHP, bsymbol = symbol, bname = name }
      loc = nearbyFreeLoc ploc state
      n = fst (scounter state)
      symbol = if n < 1 || n > 9 then '@' else Char.intToDigit n
      name = findHeroName config n
      startHP = bHP `div` (min 10 (n + 1))
      m = template mk startHP loc
      state' = state { scounter = (n + 1, snd (scounter state)) }
  in  updateLevel (updateHeroes (IM.insert n m)) state'

-- | Create a set of initial heroes on the current level, at location ploc.
initialHeroes :: Loc -> State -> State
initialHeroes ploc state =
  let k = 1 + Config.get (sconfig state) "heroes" "extraHeroes"
  in  iterate (addHero ploc) state !! k

-- Adding monsters

-- | Chance that a new monster is generated. Currently depends on the
-- number of monsters already present, and on the level. In the future,
-- the strength of the character and the strength of the monsters present
-- could further influence the chance, and the chance could also affect
-- which monster is generated.
monsterGenChance :: LevelName -> Int -> Rnd Bool
monsterGenChance (LambdaCave depth) numMonsters =
  chance $ 1%(fromIntegral (250 + 200 * (numMonsters - depth)) `max` 50)
monsterGenChance _ _ = return False

-- | Create a new monster in the level, at a random position.
addMonster :: ActorKind -> Int -> Loc -> State -> State
addMonster mk hp ploc state = do
  let loc = nearbyFreeLoc ploc state
      n = snd (scounter state)
      m = template mk hp loc
      state' = state { scounter = (fst (scounter state), n + 1) }
  updateLevel (updateMonsters (IM.insert n m)) state'

-- | Create a new monster in the level, at a random position.
rollMonster :: State -> Rnd State
rollMonster state@(State { slevel = lvl }) = do
  let hs = levelHeroList state
      ms = levelMonsterList state
  rc <- monsterGenChance (lname lvl) (L.length ms)
  if not rc
    then return state
    else do
      -- TODO: new monsters should always be generated in a place that isn't
      -- visible by the player (if possible -- not possible for bigrooms)
      -- levels with few rooms are dangerous, because monsters may spawn
      -- in adjacent and unexpected places
      loc <- findLocTry 1000 lvl
             (\ l t -> open t
                       && not (l `L.elem` L.map aloc (hs ++ ms)))
             (\ l t -> floor t
                       && L.all (\ pl -> distance (aloc pl, l) > 400) hs)
      let fmk = Frequency $ L.zip (L.map bfreq dungeonMonsters) dungeonMonsters
      mk <- frequency fmk
      hp <- randomR (bhpMin mk, bhpMax mk)
      return $ addMonster mk hp loc state