-- | Possible causes of failure of request. module Game.LambdaHack.Common.ReqFailure ( ReqFailure(..) , impossibleReqFailure, showReqFailure , permittedPrecious, permittedProject, permittedProjectAI, permittedApply #ifdef EXPOSE_INTERNAL -- * Internal operations #endif ) where import Prelude () import Game.LambdaHack.Core.Prelude import Game.LambdaHack.Common.Item import qualified Game.LambdaHack.Common.ItemAspect as IA import Game.LambdaHack.Common.Time import qualified Game.LambdaHack.Content.ItemKind as IK import qualified Game.LambdaHack.Content.RuleKind as RK import qualified Game.LambdaHack.Definition.Ability as Ability import Game.LambdaHack.Definition.Defs -- | Possible causes of failure of request. data ReqFailure = MoveUnskilled | MoveUnskilledAsleep | MoveNothing | MeleeUnskilled | MeleeSelf | MeleeDistant | MeleeNotWeapon | DisplaceUnskilled | DisplaceDistant | DisplaceAccess | DisplaceMultiple | DisplaceDying | DisplaceBraced | DisplaceImmobile | DisplaceSupported | AlterUnskilled | AlterUnwalked | AlterDistant | AlterBlockActor | AlterBlockItem | AlterNothing | CloseDistant | CloseClosed | CloseNothing | CloseNonClosable | WaitUnskilled | YellUnskilled | MoveItemUnskilled | EqpOverfull | EqpStackFull | ApplyUnskilled | ApplyFood | ApplyRead | ApplyPeriodic | ApplyOutOfReach | ApplyCharging | ApplyNoEffects | ItemNothing | ItemNotCalm | ItemOverStash | NotCalmPrecious | ProjectUnskilled | ProjectAimOnself | ProjectBlockTerrain | ProjectBlockActor | ProjectLobable | ProjectOutOfReach | ProjectFinderKeeper | NoChangeDunLeader deriving (Show, Eq) impossibleReqFailure :: ReqFailure -> Bool impossibleReqFailure reqFailure = case reqFailure of MoveUnskilled -> False -- unidentified skill items MoveUnskilledAsleep -> False -- unidentified skill items MoveNothing -> True MeleeUnskilled -> False -- unidentified skill items MeleeSelf -> True MeleeDistant -> True MeleeNotWeapon -> False -- unidentified items DisplaceUnskilled -> False -- unidentified skill items DisplaceDistant -> True DisplaceAccess -> True DisplaceMultiple -> True DisplaceDying -> True DisplaceBraced -> True DisplaceImmobile -> False -- unidentified skill items DisplaceSupported -> False AlterUnskilled -> False -- unidentified skill items AlterUnwalked -> False AlterDistant -> True AlterBlockActor -> True -- adjacent actor always visible AlterBlockItem -> True -- adjacent item always visible AlterNothing -> True -- if tile known, its properties known CloseDistant -> True CloseClosed -> True CloseNothing -> True CloseNonClosable -> True WaitUnskilled -> False -- unidentified skill items YellUnskilled -> False -- unidentified skill items MoveItemUnskilled -> False -- unidentified skill items EqpOverfull -> True EqpStackFull -> True ApplyUnskilled -> False -- unidentified skill items ApplyFood -> False -- unidentified skill items ApplyRead -> False -- unidentified skill items ApplyPeriodic -> False -- unidentified skill items ApplyOutOfReach -> True ApplyCharging -> False -- if aspect record unknown, charging unknown ApplyNoEffects -> False -- if effects unknown, can't prevent it ItemNothing -> True ItemNotCalm -> False -- unidentified skill items ItemOverStash -> True NotCalmPrecious -> False -- unidentified skill items ProjectUnskilled -> False -- unidentified skill items ProjectAimOnself -> True ProjectBlockTerrain -> True -- adjacent terrain always visible ProjectBlockActor -> True -- adjacent actor always visible ProjectLobable -> False -- unidentified skill items ProjectOutOfReach -> True ProjectFinderKeeper -> False NoChangeDunLeader -> True showReqFailure :: ReqFailure -> Text showReqFailure reqFailure = case reqFailure of MoveUnskilled -> "too low movement stat; use equipment menu to take off stat draining gear or switch to another teammate or wait until a stat draining condition passes as seen in organ menu" MoveUnskilledAsleep -> "actor asleep; yawn to wake up" MoveNothing -> "wasting time on moving into obstacle" MeleeUnskilled -> "too low melee combat stat" MeleeSelf -> "trying to melee oneself" MeleeDistant -> "trying to melee a distant foe" MeleeNotWeapon -> "trying to melee with not a weapon" DisplaceUnskilled -> "too low actor displacing stat" DisplaceDistant -> "trying to displace a distant actor" DisplaceAccess -> "trying to switch places without access" DisplaceMultiple -> "trying to displace multiple actors" DisplaceDying -> "trying to displace a dying foe" DisplaceBraced -> "trying to displace a braced foe" DisplaceImmobile -> "trying to displace an immobile foe" DisplaceSupported -> "trying to displace a foe supported by teammates or supply stash" AlterUnskilled -> "modify stat is needed to search or activate or transform terrain" AlterUnwalked -> "too low modify stat to enter or activate or transform terrain; find and equip gear that improves the stat or try with a teammate whose skill menu shows a higher stat" AlterDistant -> "trying to modify distant terrain" AlterBlockActor -> "blocked by an actor" AlterBlockItem -> "jammed by an item" AlterNothing -> "wasting time on modifying nothing" CloseDistant -> "trying to close a distant terrain" CloseClosed -> "already closed" CloseNothing -> "no adjacent terrain can be closed" CloseNonClosable -> "cannot be closed" WaitUnskilled -> "too low wait stat" YellUnskilled -> "actors unskilled in waiting cannot yell/yawn" MoveItemUnskilled -> "too low item moving stat" EqpOverfull -> "cannot equip any more items" EqpStackFull -> "cannot equip the whole item stack" ApplyUnskilled -> "too low item triggering stat" ApplyFood -> "trigger stat 1 is enough only to eat food from the ground and trigger simple appendages" ApplyRead -> "activating cultural artifacts requires trigger stat 3" ApplyPeriodic -> "manually activating periodic items requires trigger stat 4" ApplyOutOfReach -> "cannot trigger an item out of reach" ApplyCharging -> "cannot trigger an item that is still charging" ApplyNoEffects -> "cannot trigger an item that produces no effect" ItemNothing -> "wasting time on void item manipulation" ItemNotCalm -> "you try to focus on your equipment but your calm fails you" ItemOverStash -> "you roll in your hoard a little" NotCalmPrecious -> "you are too distracted to handle such an exquisite item" ProjectUnskilled -> "too low item flinging stat" ProjectAimOnself -> "cannot aim at oneself" ProjectBlockTerrain -> "aiming obstructed by terrain" ProjectBlockActor -> "aiming blocked by an actor" ProjectLobable -> "flinging a lobable item that stops at target position requires fling stat 3" ProjectOutOfReach -> "cannot aim an item out of reach" ProjectFinderKeeper -> "flinging any projectile you've found is out of the question; you prefer to keep them pristine and safe" NoChangeDunLeader -> "no manual level change for your team" -- The item should not be applied nor thrown because it's too delicate -- to operate when not calm or because it's too precious to identify by use. permittedPrecious :: Bool -> Bool -> ItemFull -> Either ReqFailure Bool permittedPrecious forced calmE itemFull@ItemFull{itemDisco} = let arItem = aspectRecordFull itemFull isPrecious = IA.checkFlag Ability.Precious arItem in if not forced && not calmE && isPrecious then Left NotCalmPrecious else Right $ IA.checkFlag Ability.Durable arItem || case itemDisco of ItemDiscoFull{} -> True ItemDiscoMean itemAspectMean -> IA.kmConst itemAspectMean || not isPrecious -- Simplified, faster version, for inner AI loop. permittedPreciousAI :: Bool -> ItemFull -> Bool permittedPreciousAI calmE itemFull@ItemFull{itemDisco} = let arItem = aspectRecordFull itemFull isPrecious = IA.checkFlag Ability.Precious arItem in (calmE || not isPrecious) && IA.checkFlag Ability.Durable arItem || case itemDisco of ItemDiscoFull{} -> True ItemDiscoMean itemAspectMean -> IA.kmConst itemAspectMean || not isPrecious permittedProject :: Bool -> Int -> Bool -> ItemFull -> Either ReqFailure Bool permittedProject forced skill calmE itemFull = let arItem = aspectRecordFull itemFull in if | not forced && skill < 1 -> Left ProjectUnskilled | not forced && IA.checkFlag Ability.Lobable arItem && skill < 3 -> Left ProjectLobable | otherwise -> permittedPrecious forced calmE itemFull -- Simplified, faster and more permissive version, for inner AI loop. permittedProjectAI :: Int -> Bool -> ItemFull -> Bool permittedProjectAI skill calmE itemFull = let arItem = aspectRecordFull itemFull in if | skill < 1 -> False | IA.checkFlag Ability.Lobable arItem && skill < 3 -> False | otherwise -> permittedPreciousAI calmE itemFull permittedApply :: RK.RuleContent -> Time -> Int -> Bool -> Maybe CStore -> ItemFull -> ItemQuant -> Either ReqFailure Bool permittedApply corule localTime skill calmE mstore itemFull@ItemFull{itemKind, itemSuspect} kit = if | skill < 1 -> Left ApplyUnskilled | skill < 2 && IK.isymbol itemKind /= IK.rsymbolNecklace (RK.ritemSymbols corule) && (IK.isymbol itemKind /= IK.rsymbolFood (RK.ritemSymbols corule) || mstore /= Just CGround) -> Left ApplyFood | skill < 3 && IK.isymbol itemKind == IK.rsymbolScroll (RK.ritemSymbols corule) -> Left ApplyRead | skill < 4 && let arItem = aspectRecordFull itemFull in IA.checkFlag Ability.Periodic arItem -> Left ApplyPeriodic -- If the item is discharged, neither the kinetic hit nor -- any effects activate, so there's no point triggering. -- Note that if client doesn't know the timeout, here we may leak the fact -- that the item is still charging, but the client risks destruction -- if the item is, in fact, recharged and is not durable -- (likely in case of jewellery). So it's OK (the message may be -- somewhat alarming though). | not $ hasCharge localTime kit -> Left ApplyCharging | otherwise -> if null (IK.ieffects itemKind) && (not itemSuspect || IA.isHumanTrinket itemKind) then Left ApplyNoEffects else permittedPrecious False calmE itemFull