!----------------------------------------------------------------
! 	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 emshort@mindspring.com 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);
];
