!---------------------------------------------------------------- ! 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 = ; ! !---------------------------------------------------------------- ! ! 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 = . ! ! 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); ];