module Dodge.Creature.State ( crUpdate, doDamage, ) where import Control.Applicative import Control.Monad import qualified Data.Map.Strict as M import Data.Maybe import Data.Monoid import Dodge.Base import Dodge.BaseTriggerType import Dodge.Corpse.Make import Dodge.Creature.Action import Dodge.Creature.Damage import Dodge.Creature.Impulse.Movement import Dodge.Creature.State.WalkCycle import Dodge.Creature.Test import Dodge.Damage import Dodge.Data.ComposedItem import Dodge.Data.Damage.Type import Dodge.Data.DoubleTree --import Dodge.Data.DoubleTree import Dodge.Data.World import Dodge.DoubleTree import Dodge.Euse import Dodge.HeldUse import Dodge.Inventory.SelectionList import Dodge.Item.BackgroundEffect import Dodge.Item.Grammar import Dodge.Item.HeldOffset import Dodge.Item.Location import Dodge.Item.MaxAmmo import Dodge.Prop.Gib import Dodge.SoundLogic import Dodge.Targeting.Draw import Dodge.Zoning.Creature import FoldableHelp import Geometry import qualified IntMapHelp as IM import LensHelp import Picture import qualified Quaternion as Q import RandomHelp import qualified SDL import Shape import ShapePicture -- this can almost certainly be made more efficient foldCr :: [Creature -> World -> World] -> Creature -> World -> World foldCr xs cr w = foldl' f w xs where f w' g = case w' ^? cWorld . lWorld . creatures . ix (_crID cr) of Just cr' -> g cr' w' Nothing -> w' {- | this seems to work, but I am not sure about the ordering: previously, the movement was updated before the ai in order to correctly set the oldpos. This should be made more sensible: should the movement side effects apply to the creature before or after it has moved? at what point invSideEffects is applied wrt to when the creature moves may affect whether the shield moves correctly -} crUpdate :: (Creature -> World -> World) -> Creature -> World -> World crUpdate f = foldCr [ doDamage -- these two , checkDeath -- must be in this order 24/7/22 , updateWalkCycle , f , invRootItemEffs ] -- I have changed the ordering of item/equipment effects, which may have -- unforseen consequences, be aware checkDeath :: Creature -> World -> World checkDeath cr w | _crHP cr > 0 = w & cWorld . lWorld . creatures . ix (_crID cr) . crDamage .~ [] | _crHP cr <= -200 = w & dropAll cr -- the order of & removecr -- these is important & stopSoundFrom (CrWeaponSound (_crID cr) 0) & addCrGibs cr | _crHP cr > -200 && _crDeathTimer cr < 5 = w & cWorld . lWorld . creatures . ix (_crID cr) . crDamage .~ [] & cWorld . lWorld . creatures . ix (_crID cr) . crDeathTimer +~ 1 | otherwise = w & dropAll cr -- the order of & removecr -- these is important & stopSoundFrom (CrWeaponSound (_crID cr) 0) & corpseOrGib cr where removecr | _crID cr == 0 = id -- cWorld . lWorld . creatures . ix (_crID cr) . crHP .~ 0 -- hack to get around player creature being killed but left with more than 0 hp | otherwise = cWorld . lWorld . creatures . at (_crID cr) .~ Nothing -- could look at the amount of damage here (given by maxDamage) too corpseOrGib :: Creature -> World -> World corpseOrGib cr = case cr ^? crDamage . to maxDamageType . _Just . _1 of Just CookingDamage -> addcorpse (thecorpse & cpSPic %~ scorchSPic) Just PoisonDamage -> addcorpse (thecorpse & cpSPic %~ poisonSPic) Just PhysicalDamage | _crPain cr > 200 -> addCrGibs cr _ -> addcorpse thecorpse where addcorpse ctype = plNew (cWorld . lWorld . corpses) cpID ctype thecorpse = makeCorpse cr scorchSPic :: SPic -> SPic scorchSPic = _1 %~ overColSH (mixColors 0.9 0.1 black . normalizeColor) poisonSPic :: SPic -> SPic poisonSPic = _1 %~ overColSH (mixColors 0.5 0.5 green . normalizeColor) {- | Drop items according to the creature state. TODO make sure this doesn't mess up any ItemPosition -} dropAll :: Creature -> World -> World dropAll cr w = foldl' (flip (dropItem cr)) w $ IM.keys $ _crInv cr doDamage :: Creature -> World -> World doDamage cr = applyPastDamages cr . applyCreatureDamage (cr ^. crDamage) cr -- TODO generalise shake to arbitrary damage amounts applyPastDamages :: Creature -> World -> World applyPastDamages cr w | _crPain cr > 200 = dojitter 3 100 | _crPain cr > 20 = dojitter 2 10 | _crPain cr > 0 = dojitter 1 1 | otherwise = w where dojitter x y = let (p, g) = runState (randInCirc x) (_randGen w) in w & cWorld . lWorld . creatures . ix (_crID cr) %~ crMvBy p & cWorld . lWorld . creatures . ix (_crID cr) . crPain -~ y & randGen .~ g -- a loop going over all root inventory items invRootItemEffs :: Creature -> World -> World invRootItemEffs cr = appEndo $ foldMap (reduceLocDT (Endo . invItemLocUpdate cr) . LocDT TopDT) (invDT' (_crInv cr)) invItemLocUpdate :: Creature -> LocationDT OItem -> World -> World invItemLocUpdate cr loc w = doAnyEquipmentEffect loc cr $ case itm ^. itType of ATTACH BULLETSYNTH -> trySynthBullet loc w ATTACH CAPACITOR -> tryDrawToCapacitor loc w EQUIP WRIST_ECG | haspulse -> tryUseParent loc w COPIER _ -> copierItemUpdate itm cr w HELD FLATSHIELD -> rootNotrootEff createShieldWall removeShieldWall itm cr w HELD MINIGUNX{} -> coolMinigun itm w HELD MACHINEPISTOL{} -> coolMachinePistol cr itm w HELD LASER | loc ^. locDT . dtValue . _2 == WeaponTargetingSF , itm ^? itLocation . ilIsAttached == Just True -> shineTargetLaser cr loc w HELD TORCH | itm ^? itLocation . ilIsAttached == Just True -> shineTorch cr loc w TARGETING tt | itm ^? itLocation . ilIsAttached == Just True -> updateItemTargeting tt cr itm w ARHUD | itm ^? itLocation . ilIsAttached == Just True -> drawARHUD loc w _ -> w where haspulse = w ^? cWorld . lWorld . creatures . ix 0 . crType . avatarPulse . pulseProgress == Just 0 itm = loc ^. locDT . dtValue . _1 coolMinigun :: Item -> World -> World coolMinigun itm | itm ^? itParams . isWarming == Just True = d . isWarming .~ False | otherwise = d . wTime %~ (max 0 . subtract 1) where d = pointerToItem itm . itParams coolMachinePistol :: Creature -> Item -> World -> World coolMachinePistol cr itm w | fromMaybe 0 (itm ^? itParams . wTime) >= _coolEnd (baseItemTriggerType itm) = w & d . wTime .~ 0 & d . coolSound .~ Nothing | fromMaybe 0 (itm ^? itParams . wTime) == _coolStart (baseItemTriggerType itm) = w & randGen .~ g & d . wTime +~ 1 & d . coolSound ?~ sid & dosound sid | fromMaybe 0 (itm ^? itParams . wTime) > _coolStart (baseItemTriggerType itm) = w & d . wTime +~ 1 & dosound (fromMaybe undefined (itm ^? itParams . coolSound . _Just)) | itm ^? itParams . isWarming == Just True = w & d . isWarming .~ False | fromMaybe 0 (itm ^? itParams . wTime) >= _warmStart (baseItemTriggerType itm) = w & d . wTime .~ _coolStart (baseItemTriggerType itm) | otherwise = w & d . wTime .~ 0 where dosound s = soundContinue (CrWeaponSound (_crID cr) (fromIntegral $ _itID itm)) (_crPos cr) s (Just 1) d = pointerToItem itm . itParams (sid, g) = runState (takeOne [whirdownSmall1S, whirdownSmall2S]) (w ^. randGen) copierItemUpdate :: Item -> Creature -> World -> World copierItemUpdate itm cr w = fromMaybe w $ do x <- itm ^? itScroll . itsInt invid <- itm ^? itLocation . ilInvID ip <- itm ^? itType . ibtPathing i <- getInventoryPath x ip invid cr itm' <- cr ^? crInv . ix i v <- getItemValue itm' w cr return $ w & pointerToItem itm . itUse . uValue .~ v doAnyEquipmentEffect :: LocationDT OItem -> Creature -> World -> World doAnyEquipmentEffect loc cr = maybe id (equipBackgroundEffect loc cr) $ loc ^? locDT . dtValue . _1 . itLocation . ilEquipSite . _Just tryUseParent :: LocationDT OItem -> World -> World tryUseParent loc w = fromMaybe w $ do t <- locUp' loc cr <- w ^? cWorld . lWorld . creatures . ix 0 return $ gadgetEffect InitialPress t cr w tryDrawToCapacitor :: LocationDT OItem -> World -> World tryDrawToCapacitor loc w = fromMaybe w $ do itm <- loc ^? locDT . dtValue . _1 i <- itm ^? itLocation . ilInvID x <- loc ^? locDT . dtValue . _1 . itConsumables . _Just guard $ x < 250 bat <- loc ^? locDT . dtLeft . ix 0 . dtValue . _1 j <- bat ^? itLocation . ilInvID y <- bat ^? itConsumables . _Just let z = min y 10 return $ w & invpoint . ix i . itConsumables . _Just +~ z & invpoint . ix j . itConsumables . _Just -~ z where invpoint = cWorld . lWorld . creatures . ix 0 . crInv trySynthBullet :: LocationDT OItem -> World -> World trySynthBullet loc w = fromMaybe w $ do i <- itm ^? itLocation . ilInvID x <- itm ^? itUse . uaParams . apInt if x < 100 then do bat <- loc ^? locDT . dtLeft . ix 0 . dtValue . _1 j <- bat ^? itLocation . ilInvID y <- bat ^. itConsumables guard $ y > 0 return $ w & invpoint . ix i . itUse . uaParams . apInt +~ 1 & invpoint . ix j . itConsumables . _Just -~ 1 else do mag <- loc ^? locDtContext . cdtParent . _1 j <- mag ^? itLocation . ilInvID y <- mag ^. itConsumables ymax <- maxAmmo mag guard $ y < ymax return $ w & invpoint . ix i . itUse . uaParams . apInt .~ 0 & invpoint . ix j . itConsumables . _Just +~ 1 where itm = loc ^. locDT . dtValue . _1 invpoint = cWorld . lWorld . creatures . ix 0 . crInv drawARHUD :: LocationDT OItem -> World -> World drawARHUD (LocDT con _) w = fromMaybe w $ do itm <- con ^? cdtParent . _1 return $ w & cWorld . lWorld . flares <>~ fold (drawTargetingAR itm w <|> drawMapperAR itm w) shineTargetLaser :: Creature -> LocationDT OItem -> World -> World shineTargetLaser cr loc w = fromMaybe (w & pointittarg . itTgPos .~ Nothing) $ do guard (crIsAiming cr) mag <- find (isammolink . (^. dtValue . _2)) (itmtree ^. dtLeft) i <- mag ^. dtValue . _1 . itConsumables guard $ i >= x maginvid <- mag ^? dtValue . _1 . itLocation . ilInvID return $ w & worldEventFlags . at InventoryChange ?~ () & cWorld . lWorld . creatures . ix (_crID cr) . crInv . ix maginvid . itConsumables . _Just -~ x & cWorld . lWorld . lasers .:~ Laser { _lpPhaseV = 1 , _lpDir = _crDir cr + argV (Q.qToV2 q) , _lpPos = pos , _lpColor = col , _lpType = TargetingLaser (_itID itm) } where o = locOrient loc cr itmtree = loc ^. locDT (p, q) = o `Q.comp` (V3 5 0 0, Q.qID) x = 1 isammolink AmmoMagSF{} = True isammolink _ = False pos = _crPos cr + xyV3 (rotate3 cdir p) cdir = _crDir cr itm = itmtree ^. dtValue . _1 pointittarg = cWorld . lWorld . creatures . ix cid . crInv . ix invid . itTargeting cid = _crID cr invid = _ilInvID $ _itLocation itm col = blue -- mixColors reloadFrac (1-reloadFrac) blue red shineTorch :: Creature -> LocationDT OItem -> World -> World shineTorch cr loc = fromMaybe id $ do mag <- find (isammolink . (^. dtValue . _2)) (itmtree ^. dtLeft) i <- mag ^. dtValue . _1 . itConsumables guard $ crIsAiming cr guard $ i >= x invid <- mag ^? dtValue . _1 . itLocation . ilInvID return $ (cWorld . lWorld . lights .:~ LSParam pos 250 0.7) . (cWorld . lWorld . creatures . ix (_crID cr) . crInv . ix invid . itConsumables . _Just -~ x) where itmtree = loc ^. locDT (p, q) = locOrient loc cr x = 10 isammolink AmmoMagSF{} = True isammolink _ = False pos = _crPos cr `v2z` 0 + rotate3 cdir (p + Q.rotate q (V3 8 0 1.5)) cdir = _crDir cr -- this probably needs to be set to null when dropped as well? -- does this need to be updated if it is not attached to the used root item? updateItemTargeting :: TargetingType -> Creature -> Item -> World -> World updateItemTargeting tt cr itm w = case tt of _ | not isattached -> w & pointittarg . itTgPos .~ Nothing & pointittarg . itTgActive .~ False TargetRBPress | rbpressed -> w & pointittarg . itTgPos %~ maybe (Just $ w ^. cWorld . lWorld . lAimPos) Just & pointittarg . itTgActive .~ True TargetRBPress -> w & pointittarg . itTgPos .~ Nothing & pointittarg . itTgActive .~ False TargetRBCreature -> w & pointittarg %~ setRBCreatureTargeting cr w TargetCursor -> w & pointittarg .~ ItTargeting (Just (w ^. cWorld . lWorld . lAimPos)) Nothing True where pointittarg = cWorld . lWorld . creatures . ix cid . crInv . ix invid . itTargeting cid = _crID cr invid = _ilInvID $ _itLocation itm isattached = itm ^?! itLocation . ilIsAttached rbpressed = SDL.ButtonRight `M.member` _mouseButtons (_input w) setRBCreatureTargeting :: Creature -> World -> ItemTargeting -> ItemTargeting setRBCreatureTargeting cr w ituse | SDL.ButtonRight `M.member` _mouseButtons (_input w) , isJust (ituse ^? itTgID . _Just) , canSeeTarget = ituse & updatePos & itTgActive .~ True | otherwise = ituse & itTgID .~ fmap _crID newtarg & updatePos & itTgActive .~ False where newtarg = safeMinimumOn (dist mwp . _crPos) . filter (canseepos . _crPos) $ crsNearCirc mwp 40 w canseepos p = hasLOS (_crPos cr) p w mwp = w ^. cWorld . lWorld . lAimPos updatePos t' = t' & itTgPos .~ posFromMaybeID (_itTgID t') posFromMaybeID Nothing = Nothing posFromMaybeID (Just i) = w ^? cWorld . lWorld . creatures . ix i . crPos canSeeTarget = fromMaybe False $ do cid <- ituse ^? itTgID . _Just cpos <- w ^? cWorld . lWorld . creatures . ix cid . crPos Just $ hasLOS cpos (_crPos cr) w --isFrictionless :: Creature -> Bool --isFrictionless cr = case cr ^? crStance . carriage of -- Just (Boosting _) -> True -- Just Floating -> True -- _ -> False