module Dodge.Creature.State ( stateUpdate , doDamage ) where import Dodge.Data import Dodge.Damage import Dodge.Hammer import Dodge.Reloading import Dodge.Prop.Gib --import Dodge.Base.Arithmetic import Dodge.Base import Dodge.Creature.State.WalkCycle import Dodge.Creature.Impulse.Movement --import Dodge.Creature.Test --import Dodge.Base import Dodge.SoundLogic import RandomHelp import Dodge.WorldEvent --import Dodge.WallCreatureCollisions import Dodge.Creature.Action --import Dodge.Hammer import Geometry import Picture import Shape import ShapePicture import qualified IntMapHelp as IM --import StrictHelp --import FoldableHelp import Data.Function import Control.Lens --import qualified Data.IntSet as IS --import Data.Maybe --import Control.Applicative --import qualified SDL --import qualified SDL.Mixer as Mix --import qualified Data.Set as S --import qualified Data.Map as M --import qualified Data.Vector as V --import qualified Data.Vector.Mutable as MV 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) . crUpdate .~ const id) . (creatures . ix (_crID cr) . crPict .~ const mempty) . (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 & _crApplyDamage cr 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)) Nothing 1 20 _ -> w | otherwise = footstepSideEffect cr w where hasJetPack = any (\it -> it ^? itType . iyBase == Just 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 . hammerPosition %~ moveHammerUp) . (useDelay . warmTime %~ decreaseToZero) . (useDelay . rateTime %~ decreaseToZero) useEquipment :: Creature -> Int -> World -> World useEquipment cr i = _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' = doItemTargeting i cr w' & maybe id (\g -> g it cr) (it ^? itEffect . ieInv) itemUpdate :: Creature -> Int -> Item -> Item itemUpdate cr i | i == crSel cr = baseupdate True | otherwise = baseupdate False where baseupdate bool = updateAutoRecharge . (itUse %~ useUpdate) . (itInvPos ?~ 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