{-# LANGUAGE PatternGuards, FlexibleContexts #-} module Combat (AttackModel, meleeAttackModel, rangedAttackModel, resolveAttack, executeAttack) where import DB import Creature import CreatureData import Tool import ToolData import Control.Monad.Error import Facing import Data.Maybe import DeviceActivation import Contact import Plane data AttackModel = RangedAttackModel CreatureRef ToolRef Device | MeleeAttackModel CreatureRef ToolRef Device | UnarmedAttackModel CreatureRef attacker :: AttackModel -> CreatureRef attacker (RangedAttackModel attacker_ref _ _) = attacker_ref attacker (MeleeAttackModel attacker_ref _ _) = attacker_ref attacker (UnarmedAttackModel attacker_ref) = attacker_ref weapon :: AttackModel -> Maybe ToolRef weapon (RangedAttackModel _ weapon_ref _) = Just weapon_ref weapon (MeleeAttackModel _ weapon_ref _) = Just weapon_ref weapon (UnarmedAttackModel {}) = Nothing instance DeviceType AttackModel where toPseudoDevice (RangedAttackModel _ _ d) = toPseudoDevice d toPseudoDevice (MeleeAttackModel _ _ d) = toPseudoDevice d toPseudoDevice (UnarmedAttackModel {}) = PseudoDevice 0 0 0 1 interactionMode :: AttackModel -> CreatureInteractionMode interactionMode (RangedAttackModel {}) = Ranged interactionMode (MeleeAttackModel {}) = Melee interactionMode (UnarmedAttackModel {}) = Unarmed -- | Get the attack model for a creature, based on whatever tool the creature is holding. -- This will fail if the creature is holding anything other than a weapon. attackModel :: (DBReadable db) => CreatureRef -> db AttackModel attackModel attacker_ref = do m_tool_ref <- dbGetWielded attacker_ref case m_tool_ref of Nothing -> return $ UnarmedAttackModel attacker_ref Just tool_ref -> do tool <- dbGetTool tool_ref case tool of DeviceTool Gun device -> return $ RangedAttackModel attacker_ref tool_ref device DeviceTool Sword device -> return $ MeleeAttackModel attacker_ref tool_ref device _ -> throwError $ DBErrorFlag ToolIs_Innapropriate -- | Get an appropriate melee attack model for a creature, based on whatever tool the creature is holding. -- This will fail if the creature is holding anything other than a suitable melee weapon (allows unarmed strike). meleeAttackModel :: (DBReadable db) => CreatureRef -> db AttackModel meleeAttackModel attacker_ref = do attack_model <- attackModel attacker_ref case interactionMode attack_model `elem` [Melee,Unarmed] of True -> return attack_model _ -> throwError $ DBErrorFlag ToolIs_Innapropriate -- | Get an appropriate ranged attack model for a creature, based on whatever tool the creature is holding. -- This will fail if the creature is holding anything other than a suitable ranged or splash weapon. rangedAttackModel :: (DBReadable db) => CreatureRef -> db AttackModel rangedAttackModel attacker_ref = do attack_model <- attackModel attacker_ref case interactionMode attack_model `elem` [Ranged,Splash] of True -> return attack_model _ -> throwError $ DBErrorFlag ToolIs_Innapropriate data AttackOutcome = AttackMiss CreatureRef (Maybe ToolRef) | AttackMalfunction CreatureRef ToolRef Integer | AttackExplodes CreatureRef ToolRef Integer | AttackHit CreatureRef (Maybe ToolRef) CreatureRef Integer | AttackDisarm CreatureRef CreatureRef ToolRef | AttackSunder CreatureRef ToolRef CreatureRef ToolRef resolveAttack :: (DBReadable db) => AttackModel -> Facing -> db AttackOutcome resolveAttack attack_model face = do device_activation <- resolveDeviceActivation (AttackSkill $ interactionMode attack_model) (DamageSkill $ interactionMode attack_model) (ReloadSkill $ interactionMode attack_model) (toPseudoDevice attack_model) (attacker attack_model) m_defender_ref <- liftM listToMaybe $ findContacts (contactMode $ interactionMode attack_model) (attacker attack_model) face case (dao_outcome_type device_activation,m_defender_ref) of (DeviceFailed, _) | Just tool_ref <- weapon attack_model -> return $ AttackMalfunction (attacker attack_model) tool_ref (dao_energy device_activation) (DeviceCriticalFailed, _) | Just tool_ref <- weapon attack_model -> return $ AttackExplodes (attacker attack_model) tool_ref (dao_energy device_activation) (DeviceActivated, Just defender_ref) -> do defense_outcome <- resolveDefense (interactionMode attack_model) defender_ref distance_squared <- liftM (fromMaybe 0) $ dbDistanceBetweenSquared (attacker attack_model) defender_ref let isDisarmingBlow = dao_skill_roll device_activation > do_skill_roll defense_outcome + distance_squared && dao_energy device_activation > do_damage_reduction defense_outcome + do_disarm_bonus defense_outcome case () of () | dao_skill_roll device_activation <= do_skill_roll defense_outcome + distance_squared -> return $ AttackMiss (attacker attack_model) (weapon attack_model) () | isDisarmingBlow && interactionMode attack_model == Unarmed, Just defender_wield_ref <- do_defender_wield defense_outcome -> return $ AttackDisarm (attacker attack_model) defender_ref defender_wield_ref () | isDisarmingBlow && interactionMode attack_model == Melee, Just weapon_ref <- weapon attack_model, Just defender_wield_ref <- do_defender_wield defense_outcome -> return $ AttackSunder (attacker attack_model) weapon_ref defender_ref defender_wield_ref () -> return $ AttackHit (attacker attack_model) (weapon attack_model) defender_ref (max 0 $ dao_energy device_activation - do_damage_reduction defense_outcome) _ -> return $ AttackMiss (attacker attack_model) (weapon attack_model) data DefenseOutcome = DefenseOutcome { do_defender_wield :: Maybe ToolRef, do_skill_roll :: Integer, do_damage_reduction :: Integer, do_disarm_bonus :: Integer } resolveDefense :: (DBReadable db) => CreatureInteractionMode -> CreatureRef -> db DefenseOutcome resolveDefense interaction_mode defender_ref = do m_tool_ref <- dbGetWielded defender_ref m_tool <- maybe (return Nothing) (liftM Just . dbGetTool) m_tool_ref disarm_bonus <- maybe (return 0) toolDurability m_tool_ref let pdevice = case m_tool of Just (DeviceTool Sword d) | interaction_mode `elem` [Melee,Unarmed] -> toPseudoDevice d _ -> PseudoDevice 0 0 0 1 device_activation <- resolveDeviceActivation (DefenseSkill interaction_mode) (DamageReductionTrait interaction_mode) InventorySkill pdevice defender_ref return $ case dao_outcome_type device_activation of DeviceActivated -> DefenseOutcome m_tool_ref (dao_skill_roll device_activation) (dao_energy device_activation) disarm_bonus DeviceFailed -> DefenseOutcome m_tool_ref 0 0 disarm_bonus DeviceCriticalFailed -> DefenseOutcome m_tool_ref 0 0 0 executeAttack :: AttackOutcome -> DB () executeAttack (AttackMiss attacker_ref m_tool_ref) = do dbPushSnapshot $ MissEvent attacker_ref m_tool_ref executeAttack (AttackHit attacker_ref m_tool_ref defender_ref damage) = do injureCreature damage defender_ref dbPushSnapshot $ AttackEvent attacker_ref m_tool_ref defender_ref executeAttack (AttackMalfunction attacker_ref tool_ref damage) = do injureCreature damage attacker_ref _ <- dbMove dbDropTool tool_ref dbPushSnapshot $ WeaponOverheatsEvent attacker_ref tool_ref return () executeAttack (AttackExplodes attacker_ref tool_ref damage) = do injureCreature damage attacker_ref dbPushSnapshot $ WeaponExplodesEvent attacker_ref tool_ref deleteTool tool_ref executeAttack (AttackDisarm attacker_ref defender_ref dropped_tool) = do dbPushSnapshot $ DisarmEvent attacker_ref defender_ref dropped_tool _ <- dbMove dbDropTool dropped_tool return () executeAttack (AttackSunder attacker_ref weapon_ref defender_ref sundered_tool) = do dbPushSnapshot $ SunderEvent attacker_ref weapon_ref defender_ref sundered_tool deleteTool sundered_tool