module Dodge.Creature.State ( stateUpdate , doDamage ) where import Dodge.Data import Dodge.ItEffect import Dodge.Euse import Dodge.EnergyBall import Dodge.Damage import Dodge.Hammer import Dodge.Reloading import Dodge.Prop.Gib import Dodge.Base import Dodge.Creature.State.WalkCycle import Dodge.Creature.Impulse.Movement import Dodge.Creature.Damage import Dodge.LightSource.Torch import Dodge.SoundLogic import RandomHelp import Dodge.Creature.Action import Geometry import Picture import Shape import ShapePicture import qualified IntMapHelp as IM import LensHelp foldCr :: [Creature -> World -> World] -> Creature -> World -> World --foldCr xs cr w = foldr ($ cr) w xs foldCr xs cr w = foldr f w xs where f g w' = case w' ^? 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 stateUpdate :: (Creature -> World -> World) -> Creature -> World -> World stateUpdate f = foldCr [ equipmentEffects , invSideEff , upInv -- upInv must be called before invSideEff 22.05.23 , movementSideEff , f , internalUpdate , clearDamage -- these three , checkDeath -- must be in , doDamage -- this order 22/06/05 ] checkDeath :: Creature -> World -> World checkDeath cr w | _crHP cr > 0 = w | otherwise = w -- & creatures . at (_crID cr) .~ Nothing & dropByState cr & removecr & stopSoundFrom (CrWeaponSound (_crID cr) 0) & corpseOrGib cr where removecr | _crID cr == 0 = (creatures . ix (_crID cr) . crType .~ NonDrawnCreature) . (creatures . ix (_crID cr) . crHP .~ 0) -- hack to get around player creature being killed but left with more than 0 hp | otherwise = creatures . at (_crID cr) .~ Nothing corpseOrGib :: Creature -> World -> World corpseOrGib cr w = case maxDamageType (_csDamage (_crState cr)) of Just (FLAMING,_) -> w & plNew corpses cpID (thecorpse & cpPict %~ fmap scorchSPic) Just (ELECTRICAL,_) -> w & plNew corpses cpID thecorpse Just (POISONDAM,_) -> w & plNew corpses cpID (thecorpse & cpPict %~ fmap poisonSPic) _ | _crPastDamage cr > 200 -> w & addCrGibs cr & bloodPuddleAt cpos & bloodPuddleAt cpos & bloodPuddleAt cpos _ -> w & bloodPuddleAt cpos & bloodPuddleAt cpos & plNew corpses cpID thecorpse where cpos = _crPos cr thecorpse = Corpse { _cpID = 0 , _cpPos = _crPos cr , _cpDir = _crDir cr , _cpPict = _crCorpse cr cr , _cpRes = Nothing } scorchSPic :: SPic -> SPic scorchSPic = over _1 $ overColSH (mixColors 0.9 0.1 black . normalizeColor) poisonSPic :: SPic -> SPic poisonSPic = over _1 $ overColSH (mixColors 0.5 0.5 green . normalizeColor) --scorchSPic :: World -> SPic -> SPic --scorchSPic w sp = evalState (scorchSPic' sp) (_randGen w) -- --scorchSPic' :: SPic -> State StdGen SPic --scorchSPic' = _1 $ overColSHM $ \col -> takeOne [col,black] ---- (return . mixColorsLinear 0.9 0.1 black) bloodPuddleAt :: Point2 -> World -> World bloodPuddleAt p w = w & snd . plNewID decorations (color (dark $ dark red) . setDepth 01 . uncurryV translate (p +.+ q) $ circleSolid 10) & randGen .~ g where (q,g) = randInCirc 10 & runState $ _randGen w internalUpdate :: Creature -> World -> World internalUpdate cr = creatures . ix (_crID cr) %~ ( (crHammerPosition %~ moveHammerUp) . stepReloading . updateMovement ) -- | Drop items according to the creature state. -- TODO make sure this doesn't mess up any ItemPosition dropByState :: Creature -> World -> World dropByState cr w = foldr (dropItem cr) w $ case cr ^. crState . csDropsOnDeath of DropAll -> IM.keys $ _crInv cr DropSpecific xs -> xs DropAmount n -> take n $ evalState (shuffle $ IM.keys $ _crInv cr) (_randGen w) clearDamage :: Creature -> World -> World clearDamage cr w = w & creatures . ix (_crID cr) . crState . csDamage .~ [] doDamage :: Creature -> World -> World doDamage cr w = w & applyPastDamages cr & applyCreatureDamage dams cr where dams = _csDamage $ _crState cr -- TODO generalise shake to arbitrary damage amounts applyPastDamages :: Creature -> World -> World applyPastDamages cr w | _crPastDamage cr > 200 = let (p,g) = runState (randInCirc 3) (_randGen w) in w & creatures . ix (_crID cr) %~ (crMvBy p . (crPastDamage -~ 100)) & randGen .~ g | _crPastDamage cr > 20 = let (p,g) = runState (randInCirc 2) (_randGen w) in w & creatures . ix (_crID cr) %~ (crMvBy p . (crPastDamage -~ 10)) & randGen .~ g | _crPastDamage cr > 0 = let (p,g) = runState (randInCirc 1) (_randGen w) in w & creatures . ix (_crID cr) %~ (crMvBy p . (crPastDamage -~ 1)) & randGen .~ g | otherwise = w movementSideEff :: Creature -> World -> World movementSideEff cr w | hasJetPack = case cr ^? crStance . carriage of Just (Boosting v) -> w & randGen .~ g & makeFlamelet (oldPos +.+ (_crRad cr + 3) *.* unitVectorAtAngle (_crDir cr + pi)) 20 (momentum +.+ 1 *.* rotateV randDir (vInverse v)) 1 20 _ -> w | otherwise = footstepSideEffect cr w where hasJetPack = any (\it -> it ^? itType . iyBase == Just (EQUIP JETPACK)) $ _crInv cr oldPos = _crOldPos cr momentum' = 0.97 *.* (_crPos cr -.- _crOldPos cr) momentum'' | magV momentum' > 3 = 3 *.* normalizeV momentum' | otherwise = momentum' momentum = momentum'' +.+ 0.01 *.* unitVectorAtAngle randAng (randDir,g) = randomR (-0.5,0.5) $ _randGen w (randAng,_) = randomR (0,2*pi) $ _randGen w useUpdate :: ItemUse -> ItemUse useUpdate = (useHammer %~ moveHammerUp) . (useDelay . warmTime %~ decreaseToZero) . (useDelay . rateTime %~ decreaseToZero) useEquipment :: Creature -> Int -> World -> World useEquipment cr i = useE (_eqUse (_eqEq $ _itUse itm)) itm cr where itm = _crInv cr IM.! i -- a map updating all inventory items upInv :: Creature -> World -> World upInv cr = creatures . ix (_crID cr) . crInv %~ IM.mapWithKey (itemUpdate cr) -- a loop going over equipped items equipmentEffects :: Creature -> World -> World equipmentEffects cr = flip (foldr $ useEquipment cr) (IM.keys $ _crInvEquipped cr) -- a loop going over all inventory items invSideEff :: Creature -> World -> World invSideEff cr w = weaponReloadSounds cr $ IM.foldrWithKey f w (_crInv cr) where f i it w' = itemInvSideEffect cr it $ doItemTargeting i cr w' & maybe id (\g -> doInvEffect g it cr) (it ^? itEffect . ieInv) itemInvSideEffect :: Creature -> Item -> World -> World itemInvSideEffect cr it | hastorchattach = createAttachLight cr it | otherwise = id where hastorchattach = it ^? itType . iyModules . ix ModHeldAttach == Just ATTACHTORCH && _itIsHeld it createAttachLight :: Creature -> Item -> World -> World createAttachLight cr it = createTorchLightOffset cr it attachoff where attachoff = it ^?! itDimension . dimAttachPos itemUpdate :: Creature -> Int -> Item -> Item itemUpdate cr i | i == crSel cr = baseupdate True | otherwise = baseupdate False where baseupdate bool = updateAutoRecharge . (itUse %~ useUpdate) . (itPos .~ InInv (_crID cr) i) . (itIsHeld .~ bool) updateAutoRecharge :: Item -> Item updateAutoRecharge it = case _itConsumption it of AutoRecharging l m t p | l < m && p <= 0 -> it & itConsumption .~ AutoRecharging (l+1) m t t | l < m -> it & itConsumption . arProgress -~ 1 _ -> it doItemTargeting :: Int -> Creature -> World -> World doItemTargeting invid cr w = case cr ^? crInv . ix invid . itTargeting of Nothing -> w Just NoTargeting -> w Just t -> let (w',t') = _tgUpdate t (_crInv cr IM.! invid) cr w t in w' & creatures . ix (_crID cr) . crInv . ix invid . itTargeting .~ t' weaponReloadSounds :: Creature -> World -> World weaponReloadSounds cr w = case cr ^? crInvSel . iselAction of Just ReloadAction{_reloadAction = la} -> soundContinue (CrReloadSound cid) (_crPos cr) (_actionSound la) (Just 1) w _ -> w -- PassiveReload stype | Just (_laReloadTime am) == am ^? laTransfer . transferTime -- -> soundContinue (CrReloadSound 0) (_crPos cr) stype Nothing w -- PassiveReload _ -> w -- ActiveClear | am ^? laTransfer . transferTime == Just 0 -> stopSoundFrom (CrReloadSound cid) w -- ActiveClear | _laTransfer am == NoTransfer -> w -- ActiveClear -> soundContinue (CrReloadSound cid) (_crPos cr) reloadS (Just 1) w -- ActivePartial{} | am ^? laTransfer . transferTime == Just 0 -> stopSoundFrom (CrReloadSound cid) w -- ActivePartial{} | _laTransfer am == NoTransfer -> w -- ActivePartial{} -> soundContinue (CrReloadSound cid) (_crPos cr) reloadS (Just 1) w -- Just ChargeableAmmo {} -> w -- Just NoConsumption {} -> w -- Just ItemItselfConsumable {} -> w -- Nothing -> w where cid = _crID cr updateMovement :: Creature -> Creature updateMovement cr | isFrictionless cr = over crPos (+.+ momentum) cr | otherwise = updateWalkCycle cr where momentum' = 0.98 *.* (_crPos cr -.- _crOldPos cr) momentum'' | magV momentum' > 1 = 1 *.* normalizeV momentum' | otherwise = momentum' momentum = momentum'' isFrictionless :: Creature -> Bool isFrictionless cr = case cr ^? crStance . carriage of Just (Boosting _) -> True Just Floating -> True _ -> False