packages feed

scroll-1.20150313: Poison.hs

module Poison where

import Control.Monad.State.Strict
import qualified Data.CaseInsensitive as CI
import qualified Data.Map as M
import Control.Applicative
import Data.Char
import Data.Maybe

import Types
import Rand
import Status
import Time
import Peruser
import Poison.Enum
import CharMap
import Player
import World

allPoisons :: M.Map (CI.CI Char) PoisonEffect
allPoisons = M.fromList
	[ (find PoisonMold, mold)
	, (find PoisonStunner, stunner)
	, (find PoisonFungus, fungus)
	]
  where
	find p = case M.lookup (Poison p) charUseMap of
		Nothing -> error $ "internal error; cannot find " ++ show p ++ " in charUseMap"
		Just c -> CI.mk c

mold :: PoisonEffect
mold c cont = do
	showMessage $ "You eat a moldy " ++ [toUpper c] ++ ". You feel lethargic.."
	origspeed <- peruseSpeed <$> gets peruser
	let newspeed = max 0 (origspeed - 2)
	let delta = origspeed - newspeed
	changePeruser $ \p -> p
		{ peruseSpeed = newspeed
		, peruseCountDown = peruseCountDown p - 1
		}
	duration <- randM $ randomR (10,15)
	delayAction duration cont $ do
		-- Note use of delta to restore, not origspeed.
		-- Other things may also be changing the peruser speed.
		changePeruser $ \p -> p { peruseSpeed = peruseSpeed p + delta }
		showMessage "You feel less lethargic now."

stunner :: PoisonEffect
stunner c cont = do
	showMessage $ "The " ++ [toUpper c] ++ " stuns you! You can't walk straight!"
	modifyPlayer $ \p -> p { playerStaggering = True }
	duration <- randM $ randomR (5,10)
	delayAction duration cont $ do
		modifyPlayer $ \p -> p { playerStaggering = False }
		len <- length . playerBody <$> gets player
		showMessage $ "You now feel more steady on your " ++ show (len * 2) ++ " tiny feet."

supportStaggaring :: Direction -> M Direction
supportStaggaring d = go . playerStaggering =<< gets player
  where
	go False = return d
	go True = do
		showMessage "Stunned, you stagger.."
		d' <- randFrom [minBound..maxBound]
		roll <- randFrom [1..10 :: Int]
		if roll < 8 && d' /= DDive
			then return d'
			else return d

fungus :: PoisonEffect
fungus = startFungalInfection

moreFungus :: PoisonEffect
moreFungus c cont = go =<< stillInfected c
  where
	go False = startFungalInfection c cont
	go True = do
		showMessage $ "Pushing through this fungus is slowing you down.."
		next $ \_ -> cont

startFungalInfection :: Char -> M NextStep -> M NextStep
startFungalInfection sporechar cont = do
	spreadFungus sporechar
	go =<< stillInfected sporechar
  where
	go True = do
		modify $ \s -> s { poisons = M.insert (CI.mk sporechar) moreFungus (poisons s) }
		showMessage $ "The fungal " ++ [toUpper sporechar] ++ " covers your body with spores! You should scrape them off somehow before they spread.."
		fork (fungalInfection sporechar) cont
	go False = do
		showMessage "You deftly avoid the fungal spores."
		cont

fungalInfection :: Char -> M NextStep
fungalInfection sporechar = go =<< stillInfected sporechar
  where
	go False = do
		showMessage "Finally you escaped the fungal infection!"
		endThread
	go True = do
		spreadFungus sporechar
		next $ \_ -> fungalInfection sporechar

spreadFungus :: Char -> M ()
spreadFungus sporechar = do
	-- Look for any whitespace surrounding the front half of
	-- the body, on the current scroll side, and plant fungus there.
	-- Note that fungus is not placed in front of the head.
	p <- gets player
	wp <- wormPositions
	let contagionzone = take ((playerLen p `div` 2) + 2) wp
	let exclusionzone = wp ++ (nearby $ playerHead p)
	wspos <- filter (`notElem` exclusionzone) . concat
		<$> mapM (findNearby isSpace) contagionzone
	mapM_ (growSpore sporechar) wspos

-- If there is no more fungus around the worm's tail the infection is over.
stillInfected :: Char -> M Bool
stillInfected sporechar = not . null . concat 
	<$> (mapM (findNearby (== sporechar)) =<< wormTailPositions)

growSpore :: Char -> Pos -> M ()
growSpore sporechar p = do
	roll <- randFrom [1..10 :: Int]
	when (roll > 3) $
		writeWorld p sporechar

nearby :: Pos -> [Pos]
nearby pos = map (flip offsetPos pos . directionOffset)
	[DLeft, DRight, DUp, DDown]

findNearby :: (Char -> Bool) -> Pos -> M [Pos]
findNearby want pos = catMaybes <$> mapM check (nearby pos)
  where
	check p = do
		v <- readWorldSafe p
		case v of
			Just c | want c -> return (Just p)
			_ -> return Nothing