!----------------------------------------------------------------
!       Moderator.h, by Emily Short
!               May be incorporated in Inform code freely with or without credit
!               and modified according to the requirements of the author.
!               Contact me at [email protected] if you encounter any bugs.
!
!       Moderator.h is primarily useful for games with a fairly complex plot
!       where the author wants to be able to block certain actions and movements
!       at some times but not at others.  Moderator does this through the
!       omnipresent "moderator" object (which supervises everything the player
!       does) and a series of scene objects.  Only one scene can be active
!       at a time.
!
!       Scenes have the following characteristics:
!
!       1. A scene can have its own set of react_befores, so that the PC is kept
!               from doing certain activities during that portion of the game.
!               Useful if a lot of the game is going to take place in the same rooms,
!               so that these properties can't be tied to location etc.
!
!       2. A scene can determine whether a player is allowed to move into a
!               certain location.
!
!       3. A scene's "trigger" property is called every turn, allowing it to do a
!               check to determine whether it is time to move on to a new scene.  It
!               is also possible to have the trigger property print something.
!
!       4. A scene ends when its endflag is set to a non-zero value, and a
!               new scene is chosen.  Scenes that have already occurred receive the
!               general attribute.
!
!----------------------------------------------------------------
!
!       INSTRUCTIONS:
!
!       Include Moderator.h after verblibm.h.
!
!       Before including the verblibm, put in
!               Replace GoSub;
!
!       Create a scene object for each discrete segment of the game for which
!       you want to be able to exert special control.
!
!       During Initialise, do
!               Moderator.current = <FirstSceneOfGame>;
!
!----------------------------------------------------------------
!
!       SCENE PROPERTIES:
!
!       locator: a routine which receives the room into which a player is moving as
!               an argument during any Go action.  It may print or not print as
!               it chooses, but should return false to permit the go action to proceed as
!               normally, true to prevent go action.  (If it prevents, it should print
!               to explain the failure, just as when preventing an action in before.)
!               The convenient thing is that this routine works regardless of how the
!               player is entering the new location and what the starting location is.
!               The presence of doors is also irrelevant.
!
!               NB: locator can be used to implement an NPC that follows the player;
!               have locator move the NPC into the new room and print something like
!               "White accompanies you as you walk."
!
!               WARNING:
!               One weird caveat: if you want to prevent the player moving into a
!               location that is dark, you will have to specify the dark room as well
!               as the actual room that's there.  Personally, these days I rarely write
!               a game that doesn't have light in all the rooms automatically.
!
!       react_before: as always.
!
!       trigger: like an each_turn property for the scene.
!
!       number: incremented each turn during the scene.  Can be used to make a scene
!               last for a set number of turns, or to schedule events at a given
!               time within the scene.
!
!       endflag: indicates whether the scene is over.  If it is positive, Moderator
!               will call the scene's advance property during this turn.  One can,
!               for instance, set the endflag to different values depending on the
!               outcome of the scene.
!
!       advance: describe the end of the scene (optionally) and set the new scene, by
!               doing Moderator.current = <name of new scene>.
!
!       startup: does anything required to set up the scene as it is beginning.
!               This routine could, for instance, move the player into a location,
!               bring an NPC on stage, etc.
!
!
!
!----------------------------------------------------------------


Object Moderator,
       with
       current 0,              !       Set to whatever the current scene is
       found_in [;
               rtrue;
       ],
       react_before [ x;
               default:
                       if (self.current && self.current provides react_before)
                       {       x = self.current.react_before();
                               return x;
                       }
                       rfalse;
       ],
       locator [ newroom i x;
               if (self.current provides locator)
               {       i = self.current.locator(newroom);
                       if (i) rtrue;
               }
               rfalse;
       ],
       each_turn [ ;
               if (self.current == 0) rfalse;
               self.current.number++;

               if (self.current provides trigger && self.current.endflag == 0)
                       self.current.trigger();

               if ((self.current == 0) || (~~(self.current provides advance))
                       || (~~(self.current provides endflag))) rfalse;

               self.advance();
       ],
       advance [;
               if (self.current.endflag == 0) rfalse;
               give self.current general;
               self.current.advance();
               if (self.current hasnt general)
                       self.current.startup();
       ],
       has scenery concealed;

Class Scene,
       with
       endflag 0,
       number 0,
       startup [;
               rfalse;
       ],
       trigger [;
               rfalse;
       ],
       advance [;
               rfalse;
       ];


!       EXAMPLE CODE:

!       Scene FirstScene,
!               with
!               react_before [;
!                       xyzzy: "You're too busy getting ready for Paris to mess around
!                               with magic spells.";
!                       listen: "Sounds like someone is banging around upstairs.";
!               ],
!               locator [ newroom;
!                       if (newroom == Attic)
!                       {       "You don't dare enter the attic while your spouse is looking
!                               for that map of Paris.";
!
!                               !       (Because this returns true, the player will never enter
!                               !       the attic during this scene.)
!                       }
!
!               ],
!               trigger [;
!                       if (self.number > 5)            !       true after 6 turns have elapsed
!                       {       self.endflag = 1;               !       this is the end of this scene
!
!                               "~Honey,~ calls a familiar voice from the attic.  ~Could you give
!                               me some help up here?~";
!                       }
!               ],
!               advance [;
!                       Moderator.current = HelpingSpouse;      !       set up the new scene
!                       "^There's a loud thumping from upstairs.  Sighing, you climb up
!                       through trap door...";
!               ];
!
!       Scene HelpingSpouse,
!               with
!               startup [;
!                       move spouse to Attic;
!                       PlayerTo(Attic);                        !       This begins the new scene
!               ],
!               locator [ newroom;
!                       if (newroom == Downstairs)
!                       {
!                               self.endflag = 1;
!                               print "~Never mind,~ you say.  ~I'm no good at this.
!                                       You'll have to find it on your own.~";
!                               rfalse;
!
!                               !       (Returns false, so the player is allowed to go, but
!                               !       the scene will end at this point.)
!                       }
!
!               ],
!               trigger [;
!                       if (MapOfParis in player)
!                       {       self.endflag = 2;
!                       }
!               ],
!               advance [;
!                       if (self.endflag == 1)
!                       {       Moderator.current = PackingAllAlone;
!                               ...
!                       }
!                       else
!                       {       Moderator.current = PackingWithSpouse;
!                               "^Now that you have the map, you head downstairs together to
!                               finish packing for Paris...";
!                       }
!               ];




!       Replacement GoSub is exactly like Graham's, except that it checks
!       with the moderator before allowing the player to make the movement

[ GoSub i j k df movewith thedir old_loc;

 if (second ~= 0 && second notin Compass
     && ObjectIsUntouchable(second)) return;

 old_loc = location;
 movewith=0;
 i=parent(player);
 if ((location~=thedark && i~=location)
     || (location==thedark && i~=real_location))
 {   j=location;
     if (location==thedark) location=real_location;
     k=RunRoutines(i,before); if (k~=3) location=j;
     if (k==1)
     {   movewith=i; i=parent(i);
     }
     else
     {   if (k==0) L__M(##Go,1,i);
         rtrue;
     }
 }

 thedir=noun.door_dir;
 if (ZRegion(thedir)==2) thedir=RunRoutines(noun,door_dir);

 j=i.thedir; k=ZRegion(j);
 if (k==3) { print (string) j; new_line; rfalse; }
 if (k==2) { j=RunRoutines(i,thedir);
             if (j==1) rtrue;
           }

 if (k==0 || j==0)
 {   if (i.cant_go ~= 0) PrintOrRun(i, cant_go);
     rfalse;
 }

 if (j has door)                               !       is the direction a door object? if so...
 {   if (j has concealed) return L__M(##Go,2);
     if (j hasnt open)
     {   if (noun==u_obj) return L__M(##Go,3,j);
         if (noun==d_obj) return L__M(##Go,4,j);
         return L__M(##Go,5,j);
     }
     k=RunRoutines(j,door_to);                                         !       where does it go?
     if (k==0) return L__M(##Go,6,j);
     if (k==1) rtrue;
     j = k;
 }

 if (Moderator.locator(j)==1) rtrue;                   !       ask the moderator

 if (movewith==0) move player to j; else move movewith to j;

 location=j; MoveFloatingObjects();

 df=OffersLight(j);
 if (df~=0) { location=j; real_location=j; lightflag=1; }
 else
 {   if (old_loc == thedark)
     {   DarkToDark();
         if (deadflag~=0) rtrue;
     }
     real_location=j;
     location=thedark; lightflag=0;
 }

 if (AfterRoutines()==1) rtrue;
 if (keep_silent==1) rtrue;
 LookSub(1);
];