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