{-# LANGUAGE PatternGuards #-} module Creature (generateInitialPlayerCreature, newCreature, Roll(..), RollComponents(..), rollCreatureAbilityScore, getCurrentCreature, getCreatureFaction, injureCreature, healCreature, getCreatureHealth, getCreatureMaxHealth, getCreatureAbsoluteHealth, getDead, deleteCreature, sweepDead) where import CreatureData import DB import SpeciesData import Species import FactionData import Control.Monad.Error import Tool import CreatureAttribute import Data.Monoid import Data.Ratio import Facing import Position import Plane import PlayerState -- | -- Generates a new Creature from the specified species. -- generateCreature :: Faction -> Species -> DB Creature generateCreature faction species = generateAttributes faction species $ mconcat $ species_starting_attributes $ speciesInfo species -- | -- During DBRaceSelectionState, generates a new Creature for the player character and sets it into the -- database's DBClassSelectionState. -- generateInitialPlayerCreature :: Species -> DB () generateInitialPlayerCreature species = do newc <- generateCreature Player species dbSetStartingRace species setPlayerState (ClassSelectionState newc) -- | -- Generates a new Creature from the specified Species and adds it to the database. -- newCreature :: (CreatureLocation l) => Faction -> Species -> l -> DB CreatureRef newCreature faction species loc = do creature <- generateCreature faction species dbAddCreature creature loc data RollComponents = RollComponents { component_base :: Integer, component_other_situation_bonus :: Integer, component_terrain_affinity_bonus :: Integer } data Roll = Roll { roll_ideal :: Integer, roll_actual :: Integer, roll_ideal_components :: RollComponents, roll_actual_components :: RollComponents, roll_log :: Integer } rollCreatureAbilityScore :: (DBReadable db) => CreatureAbility -> Integer -> CreatureRef -> db Roll rollCreatureAbilityScore score other_ideal creature_ref = do raw_ideal <- liftM (creatureAbilityScore score) $ dbGetCreature creature_ref terrain_ideal <- getTerrainAffinity creature_ref let ideal = raw_ideal + other_ideal + terrain_ideal actual <- linearRoll ideal [raw_actual, other_actual, terrain_actual] <- fixedSumLinearRoll [raw_ideal, other_ideal, terrain_ideal] actual logarithmic <- logRoll ideal --trace (show $ (score,raw_ideal,other_ideal,terrain_ideal,raw_actual,other_actual,terrain_actual)) $ return () return $ Roll ideal (if raw_actual == 0 then 0 else actual) (RollComponents raw_ideal other_ideal terrain_ideal) (RollComponents raw_actual other_actual terrain_actual) logarithmic -- | Ability bonus based on being good at working on specific types of terrain. getTerrainAffinity :: (DBReadable db) => CreatureRef -> db Integer getTerrainAffinity creature_ref = do l <- liftM (fmap location) $ getPlanarPosition creature_ref terrain_affinity_points <- case l of Nothing -> return 0 Just (plane_ref,pos) -> liftM sum $ forM [minBound..maxBound] $ \face -> do t <- terrainAt plane_ref $ offsetPosition (facingToRelative face) pos liftM (creatureAbilityScore $ TerrainAffinity t) $ dbGetCreature creature_ref return $ terrain_affinity_points `div` 4 -- | Get the current creature, if it belongs to the specified faction, based on the current playerState. getCurrentCreature :: (DBReadable db) => Faction -> db (Maybe CreatureRef) getCurrentCreature faction = do m_who <- liftM creatureOf $ playerState is_one_of_us <- maybe (return False) (liftM (== faction) . getCreatureFaction) m_who return $ if is_one_of_us then m_who else Nothing getCreatureFaction :: (DBReadable db) => CreatureRef -> db Faction getCreatureFaction = liftM creature_faction . dbGetCreature injureCreature :: Integer -> CreatureRef -> DB () injureCreature x = dbModCreature $ \c -> c { creature_damage = max 0 $ creature_damage c + x } healCreature :: Integer -> CreatureRef -> DB () healCreature = injureCreature . negate getCreatureMaxHealth :: (DBReadable db) => CreatureRef -> db Integer getCreatureMaxHealth = liftM (creatureAbilityScore ToughnessTrait) . dbGetCreature -- | Injury difference from maximum health as an integer count of hit points. getCreatureInjury :: (DBReadable db) => CreatureRef -> db Integer getCreatureInjury = liftM creature_damage . dbGetCreature -- | Health as an integer count of hit points. getCreatureAbsoluteHealth :: (DBReadable db) => CreatureRef -> db Integer getCreatureAbsoluteHealth creature_ref = liftM (max 0) $ liftM2 (-) (getCreatureMaxHealth creature_ref) (getCreatureInjury creature_ref) -- | Health as a fraction of 1. getCreatureHealth :: (DBReadable db) => CreatureRef -> db Rational getCreatureHealth creature_ref = liftM2 (%) (getCreatureAbsoluteHealth creature_ref) (getCreatureMaxHealth creature_ref) getDead :: (DBReadable db) => Reference a -> db [CreatureRef] getDead parent_ref = filterRO (liftM (<= 0) . getCreatureHealth) =<< dbGetContents parent_ref deleteCreature :: CreatureRef -> DB () deleteCreature = dbUnsafeDeleteObject $ \l -> do m_dropped_loc <- maybe (return Nothing) (liftM Just . dbDropTool) $ coerceEntityTyped _tool l return $ case m_dropped_loc of Just dropped_loc -> generalizeLocationRecord dropped_loc Nothing -> error "dbDeleteCreature: no case for this type of entity" -- | Delete all dead creatures from the database. sweepDead :: Reference a -> DB () sweepDead ref = do worst_to_best_critters <- sortByRO getCreatureHealth =<< getDead ref flip mapM_ worst_to_best_critters $ \creature_ref -> do dbPushSnapshot (KilledEvent creature_ref) deleteCreature creature_ref