! convtree.h version 1.0
! Inform extension, written for Inform 6.21 Library 6/9
!
! conversation trees, copyright 2000 Sean Barrett
! unlimited use and modification permitted
!
! convtree implements a very simple kind of conversation tree,
! in which conversations trees are encoded using object trees.
! It is designed for non-repeating conversations.
! 
! at each step in a conversation, a 'Conv' object is selected,
! and the text associated with that Conv object gets printed (or
! run if it's a routine). Now that object is "current". A "stimulus"
! is used to advance the conversation.  The stimulus can be:
!    a player command
!    a player action
!    the passing of time
! It is up to the user to encode stimuli and to trigger advancing
! conversations based on stimuli! Perhaps someone will write a
! wrapper around this that allows for NPCs who automatically do
! this. I've included a bit of sample code that shows how I used
! it, but it's probably not ideal.
!
! Given a stimulus, the current conversation object is searched
! for a new object which is listed as a response for that stimulus.
!
! convtree doesn't keep track of the current Conv object in the
! conversation; you'll want to store it on the appropriate NPC.
! What contree does do, though is take the current Conv object
! and a stimulus, and decide what to print next, and what the
! resultant Conv object will be.
!
! convtree assumes conversations will never be repeated, and explicitly
! marks Conv objects with the attribute "conv_seen"; you can check this
! in code to have dependencies on what's been said.
!
! One thing you can do is start a new conversation (e.g. you have
! a forest of conversation trees) using newconv(). Starting a new
! conversation takes the existing conversation and the one to start,
! and returns the new one. If you try to start a conversation that's
! already been had, the current one will continue instead. This is
! intended to allow you to trigger a conversation on an event, and to
! keep triggering it without bothering to check if it's happened before;
! after the first time it will have no effect.
!
! Properties you can put on a Conv object:
!
! + determine whether this convo can be triggered
!   act_trigger     action # specifying trigger for foo.acttrig()
!   obj_trigger     object # specifying trigger for foo.objtrig()
!   num_trigger     number specifying trigger for foo.numtrig()
!   obj_carried     an object which must be carried to trigger
!   obj_present     an object which must be present (e.g. the NPC!)
!   depends         a routine that should return true if it's ok to select
!                   (if you use the same routine a lot, make a class!)
!
! + what to do (only define one or the other)
!   description     text to print or run to play the event; if it returns
!                     false, it will consider itself unprinted and be willing
!                     to try again next turn
!   cbranch         another Conv object to substitute for this one
!                     (allows arbitrary graphs instead of trees)
!   cinclude        include another Conv object's children as if it were my
!                     own children (but after mine); watch out for cycles
!   deadend         include if and only if there are no children
!                     (inherit from a class, and use to avoid deadends)
!   cpop            after printing, return to parent
!
! Once you've defined these, you need to use 'em!
!
!   conv.doprint()   if the message hasn't been printed, prints the message
!                    associated with this conversation step, and marks it as
!                    having been printed
!
!   conv.acttrig(x)  returns the new conversation step given a trigger
!                    action of 'x'. If a second non-zero value is passed
!                    in, it will automatically print (if not previously)
!   conv.objtrig(x)
!   conv.numtrig(x)  as above
!
!   newconv(x,y,z)   given y as the previous conversation step, and x
!                    as the new conversation, if x hasn't been seen, returns
!                    x; otherwise returns y.  If z is non-zero, then it
!                    prints x if x is selected. (z can be omitted, equals 0)

Attribute conv_seen alias open;
Attribute immediate alias switchable;

! Need to declare the above as properties without using up
! common properties. Umm, ok, how about:
Object ConvHack with
  act_trigger, obj_trigger, num_trigger, deadend,
  obj_carried, obj_present, depends, cbranch, cinclude, cpop;

[ IndCont x y; return IndirectlyContains(x,y); ];

Class Conv with
   trigger
   [ prop val printflag i m;
      m = self;
      if (self hasnt conv_seen) return self;
      while (m) {         ! iterate through this object and cincludes
         while (m provides cpop)   ! if cpop, use parent
            m = parent(m);
         if ((~~child(m)) && m provides deadend)
            m = m.deadend;
         objectloop(i in m) {      ! look through children for a match
           if (i has conv_seen)
              continue;
           if ((~~(i provides prop)) || i.prop ~= val)
              continue;
           if (i provides obj_carried && ~~IndCont(player, i.obj_carried))
              continue;
           if (i provides obj_present && ~~IndCont(location, i.obj_present))
              continue;
           if (i provides depends && ~~i.depends())
              continue;
           if (i provides cbranch && i.cbranch has conv_seen)
              continue;

           if (i provides cbranch)
              i = i.cbranch;
           if (printflag || i has immediate)
              i.doprint();  ! hasn't been seen, so it shouldn't fail
           return i;
         }
         if (~~(m provides cinclude))
            break;
         m = m.cinclude;
      }
      return self;
   ],

   acttrig [ x y; return self.trigger(act_trigger, x, y); ],
   objtrig [ x y; return self.trigger(obj_trigger, x, y); ],
   numtrig [ x y; return self.trigger(num_trigger, x, y); ],

   doprint
   [;
      if (self hasnt conv_seen) {
         if (self.description==0 || PrintOrRun(self, description)) {
            ! special case empty description as placeholder
            give self conv_seen;
            rtrue;
         }
      }
   ],
;

[ newconv x y z;
   if (x hasnt conv_seen) {
      if (z) x.doprint();
      return x;
   }
   return y;
];

