{-# LANGUAGE PatternGuards, FlexibleContexts #-} module Combat (dbResolveRangedAttack, dbResolveMeleeAttack, dbExecuteRangedAttack, dbExecuteMeleeAttack) where import DB import DBData import Creature import CreatureData import Tool import ToolData import Control.Monad.Error import Facing import Data.Maybe import Plane import Dice import Data.List import Data.Ord import Position data RangedAttackOutcome = RangedAttackMiss CreatureRef ToolRef | RangedAttackHitCreature CreatureRef ToolRef CreatureRef Integer dbResolveRangedAttack :: (DBReadable db) => CreatureRef -> Facing -> db RangedAttackOutcome dbResolveRangedAttack attacker_ref face = do m_defender_ref <- liftM listToMaybe $ dbFindRangedTargets attacker_ref face tool_ref <- maybe (throwError $ DBErrorFlag "no-weapon-wielded") return =<< dbGetWielded attacker_ref attack_roll <- dbRollRangedAttack attacker_ref damage_roll <- dbRollRangedDamage attacker_ref tool_ref case m_defender_ref of Nothing -> return $ RangedAttackMiss attacker_ref tool_ref Just defender_ref -> do defense_roll <- dbRollRangedDefense attacker_ref defender_ref injury_roll <- dbRollInjury defender_ref damage_roll return $ case () of () | attack_roll > defense_roll -> RangedAttackHitCreature attacker_ref tool_ref defender_ref injury_roll () | otherwise -> RangedAttackMiss attacker_ref tool_ref data MeleeAttackOutcome = UnarmedAttackHitCreature CreatureRef CreatureRef Integer | UnarmedAttackMiss CreatureRef dbResolveMeleeAttack :: (DBReadable db) => CreatureRef -> Facing -> db MeleeAttackOutcome dbResolveMeleeAttack attacker_ref face = do m_defender_ref <- liftM listToMaybe $ dbFindMeleeTargets attacker_ref face attack_roll <- dbRollMeleeAttack attacker_ref damage_roll <- dbRollMeleeDamage attacker_ref case m_defender_ref of Nothing -> return $ UnarmedAttackMiss attacker_ref Just defender_ref -> do defense_roll <- dbRollMeleeDefense attacker_ref defender_ref injury_roll <- dbRollInjury defender_ref damage_roll return $ case () of () | attack_roll > defense_roll -> UnarmedAttackHitCreature attacker_ref defender_ref injury_roll () | otherwise -> UnarmedAttackMiss attacker_ref dbExecuteRangedAttack :: RangedAttackOutcome -> DB () dbExecuteRangedAttack (RangedAttackMiss attacker_ref tool_ref) = do dbPushSnapshot (MissEvent attacker_ref (Just tool_ref)) dbExecuteRangedAttack (RangedAttackHitCreature attacker_ref tool_ref defender_ref damage) = do dbPushSnapshot (AttackEvent attacker_ref (Just tool_ref) defender_ref) dbInjureCreature damage defender_ref sweepDead =<< liftM getLocation (dbWhere attacker_ref) dbExecuteMeleeAttack :: MeleeAttackOutcome -> DB () dbExecuteMeleeAttack (UnarmedAttackMiss attacker_ref) = do dbPushSnapshot (MissEvent attacker_ref Nothing) dbExecuteMeleeAttack (UnarmedAttackHitCreature attacker_ref defender_ref damage) = do dbPushSnapshot (AttackEvent attacker_ref Nothing defender_ref) dbInjureCreature damage defender_ref sweepDead =<< liftM getLocation (dbWhere attacker_ref) dbRollRangedDamage :: (DBReadable db) => CreatureRef -> ToolRef -> db Integer dbRollRangedDamage _ weapon_ref = do tool <- dbGetTool weapon_ref case tool of GunTool g -> do energy_released <- roll [0..gunEnergyOutput g] energy_throughput <- roll [0..gunThroughput g] -- todo: overheats if energy_released > energy_throughput return $ min energy_released energy_throughput dbRollMeleeDamage :: (DBReadable db) => CreatureRef -> db Integer dbRollMeleeDamage attacker_ref = liftM actual_roll $ dbRollCreatureScore MeleeDamage 0 attacker_ref dbRollRangedAttack :: (DBReadable db) => CreatureRef -> db Integer dbRollRangedAttack attacker_ref = liftM actual_roll $ dbRollCreatureScore RangedAttack 0 attacker_ref dbRollMeleeAttack :: (DBReadable db) => CreatureRef -> db Integer dbRollMeleeAttack attacker_ref = liftM actual_roll $ dbRollCreatureScore MeleeAttack 0 attacker_ref dbRollRangedDefense :: (DBReadable db,ReferenceType a) => CreatureRef -> Reference a -> db Integer dbRollRangedDefense attacker_ref x_defender_ref = do distance <- liftM (fromMaybe (error "dbGetOpposedAttackRoll: defender and attacker are on different planes")) $ dbDistanceBetweenSquared attacker_ref x_defender_ref case () of () | Just defender_ref <- coerceReferenceTyped _creature x_defender_ref -> liftM actual_roll $ dbRollCreatureScore RangedDefense distance defender_ref () | otherwise -> return distance dbRollMeleeDefense :: (DBReadable db,ReferenceType a) => CreatureRef -> Reference a -> db Integer dbRollMeleeDefense _ x_defender_ref = case () of () | Just defender_ref <- coerceReferenceTyped _creature x_defender_ref -> liftM actual_roll $ dbRollCreatureScore MeleeDefense 0 defender_ref () | otherwise -> return 1 dbFindRangedTargets :: (DBReadable db,ReferenceType x,GenericReference a S) => Reference x -> Facing -> db [a] dbFindRangedTargets attacker_ref face = do m_l <- liftM (fmap location) $ getPlanarLocation attacker_ref flip (maybe $ return []) m_l $ \(plane_ref,pos) -> liftM (mapMaybe fromLocation . sortBy (comparing (distanceBetweenSquared pos . location)) . filter ((/= generalizeReference attacker_ref) . entity) . filter (isFacing (pos,face) . location)) $ dbGetContents plane_ref dbFindMeleeTargets :: (DBReadable db,ReferenceType x,GenericReference a S) => Reference x -> Facing -> db [a] dbFindMeleeTargets attacker_ref face = do m_l <- liftM (fmap location) $ getPlanarLocation attacker_ref flip (maybe $ return []) m_l $ \(plane_ref,pos) -> liftM (mapMaybe fromLocation . filter (\x -> (location x == (offsetPosition (facingToRelative face) pos) || location x == pos) && generalizeReference attacker_ref /= entity x)) $ dbGetContents plane_ref