!===========================================================================! ! This is an EXTREMELY UNOFFICIAL pre-beta release of GNPC.h, ! ! GLYPH's NPC library. ! ! ! ! The original intent was to make this library completely independant of my ! ! other hacks, so authors can use it even if they're using the default ! ! Inform library, but to my disappointment this is completely impossible ! ! due to the way parserm.h handles conversation. My short term plan is to ! ! continue developing my library hack to handle NPCs better, but my long ! ! term plan should be to support the development of ClassAct more actively. ! ! ! ! DO NOT use this version in your own game, because the official release ! ! *will* be different, and almost definitely incompatible with this one! ! ! This is only being released for L.P. Smith's FEAMTOIF mini-comp. ! ! ! ! Feel free to peruse the code and e-mail me any suggestions / requests, ! ! but if you're going to use it in a game, do yourself a favor and wait ! ! for the official release! ! ! ! ! - GLYPH ! !---------------------------------------------------------------------------! ! some notes: ! ! ! ! When the player speaks to an NPC, the NPCmess function takes ! ! precedence over the grammar[] property and the orders[] property, ! ! to check for non-command phrases and some special-case command phrases. ! ! ! ! If NPCmess finds a match, the appropriate NPCmessage is sent. ! ! If NPCmess does not find a match, it drops to orders[]. ! ! ! ! If orders[] understands the command, it sends the appropriate command ! ! as an NPCmessage. ! ! If orders[] does not understand the command, it sends a NotUnderstood ! ! command as an NPCmessage. ! ! ! ! In other words, DON'T TOUCH the grammar[] nor the orders[] property! ! ! All conversation must be handles through the NPCmessages and the hear[] ! ! property. This is very important! See NPCmess.h for the messages. ! ! ! !===========================================================================! ! I had to change BeforeRoutines, because it wasn't designed with NPCs in mind ! so you need the line "replace BeforeRoutines;" before "include "parser"". [ BeforeRoutines l; l = ScopeCeiling(actor); if (GamePreRoutine()~=0) rtrue; if (actor == player) if (RunRoutines(actor,orders)~=0) rtrue; if (l~=0 && RunRoutines(l,before)~=0) rtrue; scope_reason=REACT_BEFORE_REASON; parser_one=0; SearchScope(ScopeCeiling(actor),actor,0); scope_reason=PARSING_REASON; if (parser_one~=0) rtrue; if (inp1>1 && RunRoutines(inp1,before)~=0) rtrue; rfalse; ]; property destination; property path; property NPC_hear; global npcflag; global npcmess_s; global npcmess_a; global npcmess_b; global npcmess_c; Class NPC with mso 0 0 0 0 0 0 0 0 0 0 0, mss 0 0 0 0 0 0 0 0 0 0 0, msa 0 0 0 0 0 0 0 0 0 0 0, msb 0 0 0 0 0 0 0 0 0 0 0, msc 0 0 0 0 0 0 0 0 0 0 0, msp 0, brs 0 0 0 0, brp 0 0 0 0, brt 0 0 0 0, bsp 0, NPCitobj 0, NPChimobj 0, NPCherobj 0, NPCthatobj 0, talktopic 0, ! problem: parserm is only calling grammar if the verb is recognized! ! I don't understand why! I hacked it to work, anyway. grammar [; if (NPCmess()) { NPCmessage(player,NPCmess_s,NPCmess_a,NPCmess_b,NPCmess_c); action = 0; noun = 0; second = 0; return 1; } return 0; ], orders [; if (action == 0) rtrue; ! text was parsed by NPCmess. NPCmessage(player,self,action,noun,second); rtrue; ], SetPro [o; if (o == self) return; self.talktopic = o; if (o has female) { self.NPCherobj = o; self.NPCthatobj = o; } else if (o has animate) { self.NPChimobj = o; self.NPCthatobj = o; } else { self.NPCitobj = o; self.NPCthatobj = o; } ], live [ i t o s a b c op sp ap bp cp; if (npcflag==1) rfalse; npcflag = 1; ! send NPC messages t = self.msp; op = self.&mso; sp = self.&mss; ap = self.&msa; bp = self.&msb; cp = self.&msc; for (i = 0: i < t : i++) { o = op-->i; s = sp-->i; a = ap-->i; b = bp-->i; c = cp-->i; self.NPC_hear(o,s,a,b,c); } self.msp = 0; ! give the NPC a chance to think o = 0; t = self.bsp; ! copy brain a = self.&brt; b = self.&brs; for (i = 0: i < t: i++) a-->i = b-->i; ! run brainstates from high to low priority until one returns true for (i = 0: i < t && o == 0: i++) if (self.brain(a-->i)) o = 1; npcflag = 0; ], NPC_go [ dest l p k dir; if (dest == 0) rfalse; ! NPC tries to get to dest p = parent(self); if (p ~= dest) { k = 0; dir = 0; ! try path l = p.&path-->(dest.destination - 1); if (l in compass) { k = p.(l.door_dir); dir = l; } if (k == 0) self.npc_wander(); else {self.GoThereNow(k,dir); rtrue;} } rfalse; ], NPC_wander [ i p j n k l dir; ! NPC wanders if (random(2) == 1) rfalse; p = parent(self); k = 0; dir = 0; !count exits & pick random one n = 0; objectloop (i in compass) { j = p.(i.door_dir); if (ZRegion(j) == 1) n++; } l = random(n); n = 0; objectloop (i in compass) { j = p.(i.door_dir); if (ZRegion(j) == 1) { n++; if (n == l) { k = j; dir = i; } } } self.GoThereNow(k,dir); rfalse; ], NPC_search [ i; ! NPC opens stuff objectloop(i has openable && i hasnt open && i has container) if (ObjectSees(self,i)) { self.Act(##open,i); if (i has open) rtrue; } rfalse; ], NPC_find [ o sa sb sc; if (ObjectSees(self,o) == 0) { if (random(2)==1) vox(self,sa); if (random(2)==1) {self.NPC_search(); rfalse;} self.NPC_wander(); rfalse; } if (o notin self) { self.Act(##take,o); if (o notin self) { if (parent(o) ofclass NPC) voxto(self,parent(o),sb,1,parent(o)); else vox(self,sc,1,parent(o)); rfalse; } } rtrue; ], GoThereNow [ k dir; if (k > 0) { if (k has door) { if (k has open) k = k.door_to; else { self.Act(##Open,k); !sact = actor;sa = action; sn = noun; ss = second; !actor = self;action = ##Open; noun = k; second = 0; !; !actor = sact;action = sa; noun = sn; second = ss; if (SameScope(self,player)) print "^"; if (k has open && k.door_to == ScopeCeiling(player)) print "Someone opens ",(the) k,".^"; } } if (k hasnt door) { if (SameScope(self,player)) { print (The)self; self.Walks(); if (dir) print " ",(name) dir; print " to ",(the) k,".^"; } else if (k == location) { print (The)self; self.Walks(); print " in from ",(the)parent(self),".^"; } move self to k; } } ], walks [; print " walks"; ], NPC_hear [o s a b c; self.def_hear(o,s,a,b,c); ], Brain [; ], BrainTest [ s i; for (i = 0: i < self.bsp : i++) if ((self.&brs)-->i == s) rtrue; rfalse; ], BrainAdd [ s p i bp bs; self.BrainRemove(s); i = self.bsp; bp = self.&brp; bs = self.&brs; if (i == self.#brp/2) { i--; if (bp-->i > p) rfalse; ! brain is full } self.bsp = i+1; while (i > 0 && bp-->(i-1) <= p) { bp-->i = bp-->(i-1); bs-->i = bs-->(i-1); i --; } bs-->i = s; bp-->i = p; rtrue; ], BrainRemove [ s i j bp bs; bp = self.&brp; bs = self.&brs; j = 0; for (i = 0: i < self.bsp: i++) if (bs-->i ~= s) { if (i ~= j) { bs-->j = bs-->i; bp-->j = bp-->i; } j ++; } i = j; while (j < self.bsp) { bs-->j = 0; bp-->j = 0; j ++; } self.bsp = i; rtrue; ], Act [ a n s sact sa sn ss; sact = actor; sa = action; sn = noun; ss = second; actor = self; switch(a) { ##Take: ; ##Open: ; ##Close: ; ##Push: ; ##PutOn: ; ##Drop: ; ##Transfer: ; ##Give: if (SameScope(self,player)) print (The)self," hands ",(the)n, " to ",(the)s,".^"; ; ##hangup: ; default: UserNPCAct(a,n,s); } actor = sact; action = sa; noun = sn; second = ss; ], def_hear ! this is fairly bunny-specific right now [o s a b c p t; if (o ofclass phone) if (o.connection) p = (o.connection).caller; else p = o; if (s == self) { ! p asks self to do a,b,c if (a == ##NotUnderStood) { self.huh(o); rfalse; } self.setpro(b); switch(a) { ##take: objectloop (t ofclass phone) if (t.caller == self) break; if (t ofclass phone) if (t.caller==self && b ofclass NPC) { self.NPC_hear(o,0,16,self,b); rfalse; } self.huh(o); ##give: objectloop (t ofclass phone) if (t.caller == self) break; if (t ofclass phone) if (t.caller==self && b ofclass NPC) { self.NPC_hear(o,0,16,self,b); rfalse; } self.huh(o); default: voxto(self,o,"What? No way.",-4,o); } } else switch(s) { default: ! s must be a string switch(a) { ! NPC library messages -1: if (b==self) voxto(self,o,"Sorry."); -2: if (b==self) voxto(self,o,"Hi.",-2,o); -3: if (b==self) voxto(self,o,"Yes what?",-1,o); -4: if (b==self) voxto(self,o,"No what?",-1,o); -5: if (b==self) voxto(self,o,"You're welcome."); -6: if (b==self) { self.SetPro(c); if (c==self) self.NPC_hear(o,s,-25); else switch(c) { biff: if (p==player) voxto(self,o,"Biff was your grandfather."); uncle: if (p==player) voxto(self,o,"He' your uncle!"); aunt: if (p==player) voxto(self,o,"She's your aunt!"); sister: if (p==player) voxto(self,o,"She's your sister!"); cousin: if (p==player) voxto(self,o,"He's your cousin!"); playerobj: if (p==player) voxto(self,o,"You're Bob Frapples!"); default: self.dunno(o,c); } } -7: if (b==self) { self.SetPro(c); switch(c) { default: self.dunno(o,c); } } -8: if (b==self) { self.SetPro(c); switch(c) { default: self.dunno(o,c); } } -9: if (b==self) { self.SetPro(c); if (c == self) voxto(self,o,"What about me?",-1,o); else switch(c) { default: self.dunno(o,c); } } -10: if (b==self) { self.SetPro(c); if (c==self) voxto(self,o,"I'm right here."); if (ObjectSees(self,c)) voxto(self,o,"That's right here."); else voxto(self,o,"I'm busy.",-4,o); } -11: if (b==self) { self.SetPro(c); if (c==self) voxto(self,o,"I'm busy."); else switch(c) { default: self.dunno(o,c); } } -12: if (b==self) switch(o) { default: voxto(self,o,"I'll talk whenever I like."); } -13: if (b==self) { self.SetPro(c); if (c==self) voxto(self,o,"I'm just fine."); else switch(c) { playerobj: if (p==player) voxto(self,o,"You're doing fine."); default: if (p ~= o) voxto(self,o,"I can't tell from here."); else self.dunno(o,c); } } -14: if (b==self) voxto(self,o,"Bye.",-14,o); -15: if (b==self) voxto(self,o,"What's so funny?",-1,o); -16: if (b==self) voxto(self,o,"Hmm.",-17,o); -17: if (b==self) voxto(self,o,"What?",-1,o); -18: if (b==self) voxto(self,o,"Erm...",-17,o); -19: if (b==self) { self.SetPro(c); switch(c) { default: self.dunno(o,c); } } -20: if (b==self) voxto(self,o,"Sorry.",-1,o); -21: if (b==self) voxto(self,o,"Anything you like."); -22: if (b==self) voxto(self,o,"Yeah."); -23: if (b==self) { self.SetPro(c); if (ObjectSees(b,c)) voxto(self,o,"Right here."); else switch(c) { default: self.dunno(o,c); } } -24: if (b==self) { self.SetPro(c); if (c in b) switch(c) { default: self.dunno(o,c); } else self.huh(o); } -25: if (b==self) { voxto(self,o,"My name isn't important."); } -26: if (b==self) { self.SetPro(c); switch(c) { coprophilia: voxto(self,o,"Why no, I don't. And wash your hands, would ya?"); default: self.dunno(o,c); } } -28: if (b==self) { self.SetPro(c); if (c==self) voxto(self,o,"Yes."); else voxto(self,o,"No."); } ! user messages 1: self.huh(); 3: voxto(self,o,"I don't have any particular quest."); 4: voxto(self,o,"Blue."); 5: voxto(self,o,"I do my own hair, thanks."); 6: voxto(self,o,"A woodchuck would chuck as much wood as he could if a woodchuck could chuck wood."); 7: voxto(self,o,"Who's there?"); 8: voxto(self,o,"I'm not thirsty."); 9: voxto(self,o,"What? They were supposed to remove that!"); 10: voxto(self,o,"Xyxxy?"); 11: voxto(self,o,"42."); 14: if (o ofclass phone) if (o.caller == self) self.Act(##hangup,o); 15: if (o ofclass phone) if (o.hungup) { self.Act(##take,o); voxto(self,o,"Hello?",-2); } 16: if (b==self) { objectloop (t ofclass phone) if (t.caller == self && SameScope(t,self)) break; if (t ofclass phone) { if (~~(t.caller==self)) { self.huh(); rfalse; } if (~~SameScope(b,c)) { voxto(self,o,"They're not here.",-4); rfalse; } voxto(self,c,"Here. It's for you.",17); rfalse; } self.huh(o); rfalse; } 17: objectloop (t ofclass phone) if (t.caller == o && SameScope(t,self)) break; if (t ofclass phone) if (~~(t.caller==o)) { self.huh(); rfalse; } t.caller = self; voxto(self,t,"Hello?",-2); } } ], Huh [ o; switch(random(10)) { 1: voxto(self,o,"What?",-1,o); 2: voxto(self,o,"Wha?",-1,o); 3: voxto(self,o,"Eh?",-1,o); 4: voxto(self,o,"Uh..",-1,o); 5: voxto(self,o,"Could you elaborate?",-1,o); 6: voxto(self,o,"What are you talking about?",-1,o); 7: voxto(self,o,"Be a little less vague, there.",-1,o); 8: voxto(self,o,"Um..",-1,o); 9: voxto(self,o,"Try me again.",-1,o); default: voxto(self,o,"Huh?",-1,o); } ], Dunno [ o c; if (c == 0) self.Huh(o); else switch(random(3)) { 1: voxto(self,o,"I have no idea."); 1: voxto(self,o,"I don't know."); default: self.Huh(o); } ], Daemon [; self.live(); ]; [ UserNPCAct; ! stub ]; [ emit o s a b c; if (s == 0) rfalse; if (SameScope(o,player) && o ~= player) print (string) s; if (a|b|c ~= 0) npcmessage(o,s,a,b,c); ]; [ vox o s a b c; if (s == 0) rfalse; if (SameScope(o,player) && o ~= player) print (The)o, " says, ~", (string) s, "~^"; npcmessage(o,s,a,b,c); ]; [ voxto o i s a b c; if (s == 0) rfalse; if (i == o) {vox(o,s,a,b,c); rtrue;} if (SameScope(o,player) && o ~= player) print (The)o, " says (to ",(the)i,"), ~", (string)s, "~^"; npcmessage(o,s,a,b,c); ]; [ whisperto o i s a b c; if (s == 0) rfalse; if (SameScope(o,player) && i == player) print (The)o, " whispers, ~", (string) s, "~^"; if (SameScope(o,i)) NPCmessadd(o,i,s,a,b,c); ]; [ NPCmessage o s a b c i; #IFDEF DEBUG; if (parser_trace >= 1) print "[ Sending ~",(The)o," ",s," ",a," ",b," ",c,"~ ]^"; #ENDIF; objectloop(i ~= player) { if (o ~= i && i provides NPC_hear) if (SameScope(o,i)) { if (i ofclass NPC) {if (a|b|c ~= 0) NPCmessadd(o,i,s,a,b,c);} else i.NPC_hear(o,s,a,b,c); } } ]; [ NPCmessadd o i s a b c j; if (i.msp < i.#mso/2) i.msp++; if (i.msp < i.#mso/2) { j = i.msp-1; (i.&mso)-->j = o; (i.&mss)-->j = s; (i.&msa)-->j = a; (i.&msb)-->j = b; (i.&msc)-->j = c; } ]; #IFDEF DEBUG; Verb meta 'brain' * multi -> printbrain; [ printbrainSub i; if (noun ofclass NPC) { print (The) noun, "'s brain: S ["; for (i = 0: i < noun.#brs/2 : i++) { print " ",(noun.&brs)-->i; } print " ] P ["; for (i = 0: i < noun.#brp/2 : i++) { print " ",(noun.&brp)-->i; } print " ] BSP = ",noun.bsp,"^^"; } return; ]; #ENDIF; [ GamePreRoutine; if (action == ##take or ##open or ##close or ##push or ##puton or ##drop or ##transfer or ##give) NPCmessage(actor,action,noun,second,0); rfalse; ]; NPC NPCeveryone with name 'everyone' 'all' 'everbody' 'myself' 'self' 'anyone' 'anybody', found_in [; return location; ], grammar [o r s a b c; actor = -10000; r = NPCmess(); if (NPCmess_s == -10000) s = 1; if (NPCmess_a == -10000) a = 1; if (NPCmess_b == -10000) b = 1; if (NPCmess_c == -10000) c = 1; objectloop(o provides NPC_hear) if (SameScope(o,player)) { if (s) NPCmess_s = o; if (a) NPCmess_a = o; if (b) NPCmess_b = o; if (c) NPCmess_c = o; NPCmessage(player,NPCmess_s,NPCmess_a,NPCmess_b,NPCmess_c); } action = 0; noun = 0; second = 0; actor = self; return r; ], orders [o; if (action == 0) rtrue; ! text was parsed by NPCmess. objectloop(o provides NPC_hear) if (SameScope(o,player)) NPCmessage(player,o,action,noun,second); rtrue; ], has animate static concealed; ! here's a telephone class just to make things fun Class phone with description [; print "It's a fairly standard looking phone. Printed just below the keypad is the number ",self.numbera,"-",self.numberb,"."; ], name 'phone' 'telephone' 'keypad' 'receiver', short_name "telephone", numbera -1, numberb -1, connection 0, caller 0, hungup 1, grammar [; if (self.hungup) "The phone is hung up."; if (~~self.connection) "Dialtone."; if (~~(self.connection).caller) "There is no answer."; if (actor.connection.hungup) "The other line is still ringing."; if (actor.connection.caller) actor = actor.connection.caller; ! print "[ phone actor = ",(the)actor," ]^"; if (NPCmess()) { NPCmessage(self.connection,NPCmess_s,NPCmess_a,NPCmess_b,NPCmess_c); action = 0; noun = 0; second = 0; actor = self; return 1; } actor = self; return 0; ], orders [; if (action == 0) rtrue; ! text was parsed by NPCmess. NPCmessage(self.connection,(self.connection).caller,action,noun,second); rtrue; ], daemon [; if (self.caller) if (~~SameScope(self.caller,self)) self.caller = 0; if (self.connection) if ((self.connection).hungup) emit(self.connection,"The phone is ringing.^",15); ], before [; take: if (~~self.hungup) { if (actor == player) print "The telephone is already off its hook.^"; rtrue; } self.hungup = 0; self.caller = actor; if (actor == player) print "You pick up the receiver."; else if (SameScope(actor,player)) print (The)actor, " picks up the receiver."; if ((~~self.connection) && (actor == player)) " Dialtone."; "^"; hangup: if (self.hungup) { if (actor == player) print "The telephone is already on its hook.^"; rtrue; } self.hungup = 1; self.caller = 0; if (actor == player) print "You place the receiver back on the hook.^"; else if (SameScope(actor,player)) print (The)actor, " places the receiver back on the hook.^"; if (self.connection) { if (~~(self.connection).hungup) emit(self.connection,"The phone goes ~CLICK~!^",14); (self.connection).connection = 0; self.connection = 0; } rtrue; ], NPC_hear [o s a b c r; r = self.connection; if (~~r) rfalse; if (r.hungup) rfalse; self.caller = o; if (s ofclass string) { if (SameScope(self,r)) rfalse; if (SameScope(r,player)) print "Through ",(the)r," you hear ",(the)o," say, ~",(string)s,"~^"; npcmessage(r,s,a,b,c); rtrue; } ], has talkable; Verb 'dial' 'call' * number number -> Dial; Verb 'hang' * 'up' noun -> Hangup * noun 'up' -> Hangup; [ DialSub o a b; objectloop(o ofclass phone && ~~a) if (SameScope(o,actor)) a = o; if (~~a) rfalse; if (a.hungup) "You'll have to pick up the receiver first."; if (a.connection) "You'll have to hang up first."; objectloop(o ofclass phone && ~~b) if (o.numbera == noun && o.numberb == second) b = o; if (~~b) "A recorded voice drones ~The number you have dialed is not in service.~"; if (~~b.hungup) "Busy signal."; a.connection = b; b.connection = a; startdaemon(a); startdaemon(b); if (actor == player) print "It's ringing.^"; emit(b,"The phone is ringing.",15); ]; [ hangupSub; "That's not something you can hang up."; ];