packages feed

MazesOfMonad-1.0.4: src/MoresmauJP/Rpg/NPC.hs

-- | NPC template definitions and character creation
-- (c) JP Moresmau 2009
module MoresmauJP.Rpg.NPC where

import Control.Monad

import Data.Array.IArray
import Data.Maybe
import Data.List
import qualified Data.Map as M
import MoresmauJP.Rpg.Actions
import MoresmauJP.Rpg.Inventory
import MoresmauJP.Rpg.Items
import MoresmauJP.Rpg.Character
import MoresmauJP.Util.Lists
import MoresmauJP.Util.Numbers
import MoresmauJP.Util.Random

troll=NPCTemplate {
	typeName="Troll",
	creatureType=Humanoid,
	attitudeRange=(3,8),
	traitRanges=[
		(Strength,(18,24)),
		(Dexterity,(7,11)),
		(Constitution,(16,26)),
		(Willpower,(10,12)),
		(Intelligence,(4,8)),
		(Balance,(4,8)),
		(Charisma,(3,5)),
		(Perception,(4,8))],
	possibleItems=[
		(RightHand,[
			(Nothing,3),
			(Just club,2),
			(Just bigclub,3)]
			)],
	possibleGold=(0,10)		
	}

goblin=NPCTemplate {
	typeName="Goblin",
	creatureType=Humanoid,
	attitudeRange=(5,10),
	traitRanges=[
		(Strength,(5,11)),
		(Dexterity,(8,13)),
		(Constitution,(7,12)),
		(Willpower,(5,8)),
		(Intelligence,(6,10)),
		(Balance,(4,8)),
		(Charisma,(3,7)),
		(Perception,(4,8))],
	possibleItems=[
		(RightHand,[
			(Just sword,2),
			(Just dagger,2),
			(Just hatchet,1)]
			),
		(LeftHand, [
			(Nothing,2),
			(Just smallShield,1)]
			),
		(Body, [
			(Nothing,2),
			(Just leatherArmor,1)
			]
			)
		],
	possibleGold=(0,30)	
	}

kobold=NPCTemplate {
	typeName="Kobold",
	creatureType=Humanoid,
	attitudeRange=(5,12),
	traitRanges=[
		(Strength,(4,8)),
		(Dexterity,(8,11)),
		(Constitution,(4,10)),
		(Willpower,(5,8)),
		(Intelligence,(7,12)),
		(Balance,(6,12)),
		(Charisma,(5,9)),
		(Perception,(8,12))],
	possibleItems=[
		(RightHand,[
			(Just sword,1),
			(Just dagger,3),
			(Just hatchet,1)]
			)], 
	possibleGold=(0,7)		
	}



giantRat=NPCTemplate {
	typeName="Giant Rat",
	creatureType=Animal,
	attitudeRange=(5,10),
	traitRanges=[
		(Strength,(3,6)),
		(Dexterity,(4,8)),
		(Constitution,(3,7)),
		(Willpower,(2,4)),
		(Intelligence,(2,3)),
		(Balance,(5,10)),
		(Charisma,(1,2)),
		(Perception,(4,8))],
	possibleItems=[],
	possibleGold=(0,0)	
	}

snake=NPCTemplate {
	typeName="Snake",
	creatureType=Animal,
	attitudeRange=(5,10),
	traitRanges=[
		(Strength,(5,8)),
		(Dexterity,(12,16)),
		(Constitution,(5,10)),
		(Willpower,(6,10)),
		(Intelligence,(2,4)),
		(Balance,(5,10)),
		(Charisma,(1,4)),
		(Perception,(8,10))],
	possibleItems=[],
	possibleGold=(0,0)	
	}	
	
vampire=NPCTemplate {
	typeName="Vampire",
	creatureType=Humanoid,
	attitudeRange=(3,8),	
	traitRanges=[
		(Strength,(12,18)),
		(Dexterity,(10,14)),
		(Constitution,(16,24)),
		(Willpower,(14,22)),
		(Intelligence,(14,20)),
		(Balance,(8,14)),
		(Charisma,(10,15)),
		(Perception,(6,10))],
	possibleItems=[],
	possibleGold=(10,60)	
	}
	
ghoul=NPCTemplate {
	typeName="Ghoul",
	creatureType=Humanoid,
	attitudeRange=(1,6),	
	traitRanges=[
		(Strength,(8,14)),
		(Dexterity,(8,12)),
		(Constitution,(18,24)),
		(Willpower,(10,14)),
		(Intelligence,(6,10)),
		(Balance,(6,10)),
		(Charisma,(4,8)),
		(Perception,(3,7))],
	possibleItems=[],
	possibleGold=(0,20)	
	}

outlaw=NPCTemplate {
	typeName="Outlaw",
	creatureType=Human,
	attitudeRange=(8,12),	
	traitRanges=[
		(Strength,(8,14)),
		(Dexterity,(8,14)),
		(Constitution,(8,12)),
		(Willpower,(5,8)),
		(Intelligence,(6,10)),
		(Balance,(8,12)),
		(Charisma,(6,10)),
		(Perception,(10,15))],
	possibleItems=[(RightHand,[
			(Just sword,2),
			(Just hatchet,1)]
			),
		(LeftHand, [
			(Nothing,1),
			(Just smallShield,1),
			(Just dagger,2)
			]
			),
		(Body, [
			(Nothing,2),
			(Just leatherArmor,2),
			(Just chainMail,1)
			]
			),
		(Head,[
			(Nothing, 3),
			(Just leathercap,1),
			(Just helmet,1)
			])],
		possibleGold=(0,50)		
	}

blackKnight=NPCTemplate {
	typeName="Black Knight",
	creatureType=Human,
	attitudeRange=(5,10),	
	traitRanges=[
		(Strength,(14,18)),
		(Dexterity,(14,18)),
		(Constitution,(14,18)),
		(Willpower,(10,15)),
		(Intelligence,(8,12)),
		(Balance,(8,12)),
		(Charisma,(8,14)),
		(Perception,(8,14))],
	possibleItems=[(RightHand,[
			(Just sword,2),
			(Just twoHandedWord,1),
			(Just battleaxe,1)]
			),
		(LeftHand, [
			(Nothing,2),
			(Just smallShield,2),
			(Just bigShield,1)
			]
			),
		(Body, [
			(Just leatherArmor,3),
			(Just chainMail,2),
			(Just fullPlate,1)
			]
			),
		(Head,[
			(Nothing, 3),
			(Just helmet,2),
			(Just heaume,1)
			])],
	possibleGold=(10,30)	
	}

minotaur=NPCTemplate {
	typeName="Minotaur",
	creatureType=Animal,
	attitudeRange=(2,5),	
	traitRanges=[
		(Strength,(14,22)),
		(Dexterity,(10,16)),
		(Constitution,(14,22)),
		(Willpower,(10,16)),
		(Intelligence,(6,10)),
		(Balance,(10,14)),
		(Charisma,(4,8)),
		(Perception,(5,10))],
	possibleItems=[],
	possibleGold=(0,0)
	}

peddler=NPCTemplate {
	typeName="Peddler",
	creatureType=Human,
	attitudeRange=(20,20),
	traitRanges=[
		(Strength,(6,12)),
		(Dexterity,(8,13)),
		(Constitution,(8,14)),
		(Willpower,(5,12)),
		(Intelligence,(8,14)),
		(Balance,(6,12)),
		(Charisma,(10,14)),
		(Perception,(8,12))],
	possibleItems=[
		(RightHand,[
			(Nothing,3),
			(Just dagger,2),
			(Just sword,3)]
			),
		(Body, [
			(Nothing,3),
			(Just leatherArmor,1)
			]
			)],
	possibleGold=(100,200)		
	}


allNPCTemplates :: [NPCTemplate]
allNPCTemplates= [troll,goblin,kobold,giantRat,snake,vampire,ghoul,outlaw,blackKnight,minotaur,peddler]

templateAttitudeNoChange=[peddler]

getNPCOccurences :: Int -> [(NPCTemplate,Int)]
getNPCOccurences characterLevel=
	let 
		totalTemplates=length allNPCTemplates
		ponderedLevel=div (characterLevel * 8) 10
		likelyhood npc=(npc,totalTemplates - (abs ((templateLevel npc) - ponderedLevel)))
	in map likelyhood allNPCTemplates
		
templateLevel :: NPCTemplate -> Int
templateLevel t=avg $ map (\(_,(low,high))-> div (low+high) 2) (traitRanges t)
		
type CharacteristicRanges=Array Characteristic (Int,Int)

data NPCTemplate = NPCTemplate {
	typeName::Name,
	creatureType::NPCType,
	attitudeRange::(Int,Int),
	traitRanges::[(Characteristic,(Int,Int))],
	possibleItems::[(Position,[(Maybe ItemType,Int)])],
	possibleGold::(Gold,Gold)
	} deriving (Show,Read,Eq)

data NPCCharacter = NPCCharacter {
	npcCharacter::Character,
	npcType::NPCType,
	npcAttitude::Int
	}deriving (Show,Read,Eq)

data NPCType=Animal | Humanoid | Human
	deriving (Show,Read,Eq,Ord,Enum,Bounded)

{--data AttitudeLevel=Rather | Quite | Very
	deriving (Show,Read,Eq,Ord,Enum,Bounded)

data Attitude=Friendly | Neutral | Aggressive | Hostile
	deriving (Show,Read,Eq,Ord,Enum,Bounded)

allAttitudeLevels=[(x,y) | y<-[Friendly .. Hostile], x<-[Rather .. Very]]
--}

data InteractionFromNPC=NPCFight | NPCTrade | NPCWait
	deriving (Show,Read,Eq,Enum,Bounded)

getInitialAttitude :: (MonadRandom m)=> NPCCharacter -> m (InteractionFromNPC)
getInitialAttitude NPCCharacter{npcAttitude=att}=do
	r <- (roll d20)
	let (rr,_)=evalResult r att
	return (case rr of
		Failure {}	-> NPCFight
		Success	{}	-> NPCWait)

data FightAttitude = ContinueFight | OfferBribe Int | PrayForClemency | TryEscape 
	deriving (Show,Read,Eq)

data FightInfo=FightInfo {
		npcLosses :: M.Map Characteristic Int,
		cLosses :: M.Map Characteristic Int,
		hasPrayed :: Bool,
		hasBribed :: Bool
	}
	deriving (Show,Read,Eq)

newFightInfo :: FightInfo
newFightInfo=FightInfo M.empty M.empty False False

addNPCLosses:: FightInfo -> NPCCharacter -> NPCCharacter -> FightInfo
addNPCLosses fi c1 c2=fi{npcLosses=addLosses (npcLosses fi) (npcCharacter c1) (npcCharacter c2)}

addNPCLoss:: FightInfo -> Characteristic -> Int -> FightInfo
addNPCLoss fi c v=fi{npcLosses=addToFightInfo (npcLosses fi) (c,v)}

addCharacterLoss:: FightInfo -> Characteristic -> Int -> FightInfo
addCharacterLoss fi c v=fi{cLosses=addToFightInfo (cLosses fi) (c,v)}

addCharacterLosses:: FightInfo -> Character -> Character -> FightInfo
addCharacterLosses fi c1 c2=fi{cLosses=addLosses (cLosses fi) c1 c2}


addLosses :: M.Map Characteristic Int -> Character -> Character -> M.Map Characteristic Int
addLosses m1 c1 c2= let
	both=zipWith (\(c1,r1) (_,r2) -> (c1,(getR Current r1)-(getR Current r2))) (assocs $ traits c1) (assocs $ traits c2)
	changed=filter (\((_,diff))->diff>0) both
	in foldl addToFightInfo m1 changed

addToFightInfo :: M.Map Characteristic Int -> (Characteristic, Int) -> M.Map Characteristic Int
addToFightInfo m1 (c,v)=let
	i=M.lookup c m1
	in case i of
		Nothing -> M.insert c v m1
		Just v1 -> M.insert c (v+v1) m1

getFightAttitudes :: NPCCharacter -> FightInfo -> [(FightAttitude,Int)]
getFightAttitudes npc fi=let 
	fight=21-(npcAttitude npc)
	percentPhysical=getCurrentPercentOfNormal (npcCharacter npc) Physical
	percentMental=getCurrentPercentOfNormal (npcCharacter npc) Mental
	npcLossesSum=(M.fold (+) 0 (npcLosses fi))
	cLossesSum=(M.fold (+) 0 (cLosses fi))
	percentLoss=if npcLossesSum>0
		then (cLossesSum * 100) `div` npcLossesSum
		else 100
	escape=max 0 (if (percentPhysical<11)
		then 10+(11-percentPhysical)
		else if (percentMental<11)
			then 10+(11-percentMental)
			else 10-((percentPhysical + percentMental) `div` 10))
		+ (if (percentLoss<80)
			then (10 - (percentLoss `div` 10))
			else 0
		)
	pray=if (hasPrayed fi || (npcType npc)==Animal) 
		then 0
		else div escape 2
	gold=getGold $ inventory $ npcCharacter npc
	bribe=if gold>0 && (not $ hasBribed fi)  && ((npcType npc)/=Animal)
		then div escape 2
		else 0
	in [(ContinueFight,fight),(TryEscape,escape),(PrayForClemency,pray),(OfferBribe gold,bribe)]

getFightAttitude :: (MonadRandom m)=> NPCCharacter -> FightInfo -> m (FightAttitude,FightInfo)
getFightAttitude npc fi = do
	fa<-randomPickp $ occurenceList $ getFightAttitudes npc fi
	let fi2= case fa of
		PrayForClemency -> fi{hasPrayed=True}
		OfferBribe _->fi{hasBribed=True}
		_ -> fi
	return (fa,fi2)

data InteractionToNPC=Ignore | Fight | Trade | Convert | Steal 
	deriving (Show,Read,Eq,Enum,Bounded)

possibleNPCInteractions :: NPCCharacter -> [InteractionToNPC]
possibleNPCInteractions NPCCharacter{npcType=tp}=case tp of 
	Animal 		-> [Ignore .. Fight]
	Humanoid 	-> [Ignore .. Trade]
	Human 		-> [Ignore .. Steal]

maxPosBag :: NPCTemplate -> Int
maxPosBag nt=case creatureType nt of
	Animal 		-> 0
	Humanoid 	-> 0
	Human 		-> 10

generateFromTemplate :: (MonadRandom m)=> NPCTemplate -> Int -> m (NPCCharacter)
generateFromTemplate template attitudeModifier=do
	let name=typeName template
	-- get gender
	rnd <- getRandomRange (1,2)
	let gender= if rnd==1
		then Male
		else Female
	-- generate ratings
	ranges<-mapM generateFromRange (sortBy (\x y-> compare (fst x) (fst y)) (traitRanges template))
	-- fill ratings with health
	let traits=getDefaultHealth $ array (head allCharacteristics,last allCharacteristics) ranges
	-- get item or Nothing for each position
	inv<-mapM (\(pos,items)->do
		it<-randomPickp (occurenceList items)
		return (pos,it)) (possibleItems template)
	-- only keep items, filter out Nothings
	let inv2=map (\(x,y)->(x,fromJust y)) (filter (isJust . snd) inv)
	gold<-getRandomRange $ possibleGold template
	attitudeLevel<-liftM (	if (elem template templateAttitudeNoChange)
			then id
			else ((max 1) . (\x->x - attitudeModifier)))
		(getRandomRange $ attitudeRange template)
	let maxPos=maxPosBag template
	return (NPCCharacter (Character {
		name=name,
		gender=gender,
		traits=traits,
		inventory=makeFullInventory inv2 maxPos gold,
		affects=[],
		spells=[]
	}) (creatureType template) attitudeLevel)
	
generateFromRange :: (MonadRandom m)=>(Characteristic,(Int,Int))->m (Characteristic,Rating)
generateFromRange (c,(low,high))=do 
	rnd<-getRandomRange (low,high)
	return (c,mkRating rnd)