-- | NPC template definitions and character creation -- (c) JP Moresmau 2009 module MoresmauJP.Rpg.NPC where import Data.Array.IArray import Data.Maybe import Data.List 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,11)), (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,3), (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,(3,6)), (Dexterity,(12,16)), (Constitution,(5,10)), (Willpower,(6,10)), (Intelligence,(2,4)), (Balance,(5,10)), (Charisma,(1,2)), (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,(10,14)), (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,2), (Just smallShield,1), (Just dagger,2) ] ), (Body, [ (Nothing,3), (Just leatherArmor,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,(12,18)), (Dexterity,(12,18)), (Constitution,(12,18)), (Willpower,(10,15)), (Intelligence,(6,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,20)), (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] 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 :: (Monad m)=> NPCCharacter -> RandT m (InteractionFromNPC) getInitialAttitude NPCCharacter{npcAttitude=att}=do r <- (roll d20) let (rr,_)=evalResult r att return (case rr of Failure {} -> NPCFight Success {} -> NPCWait) 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 :: (Monad m)=> NPCTemplate -> RandT m (NPCCharacter) generateFromTemplate template=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<-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 :: (Monad m)=>(Characteristic,(Int,Int))->RandT m (Characteristic,Rating) generateFromRange (c,(low,high))=do rnd<-getRandomRange (low,high) return (c,mkRating rnd)