#charset "us-ascii" #include #include /* * SCENES: A Scene Control Extension *. by Eric Eve *. Version 1.0; 12-Jul-09 * * Thie SCENES Extension provides a means of defining and manipulating * Scenes in a way similar to the Scenes mechanism available in Inform 7. * * For a simple Scene, define an object of class Scene, define the * condition that starts the Scene in its startsWhen property and the * condition that ends the scene in its endsWhen property. Override * startUp() and finishOff(howEnded) to define what happens when this * scene begins and ends, for example: * *. trainStop: Scene *. startsWhen = (gPlayerChar.isIn(platform)) *. endsWhen = (gRevealed('dinner-date')) *. *. startUp() *. { *. train.moveInto(platform); *. "A train pulls into the station and pulls up at the platform. "; *. } *. *. finishOff(howEnded) *. { *. train.moveInto(nil); *. "The train starts moving and accelerates down the track. "; *. } *. ; * * We can test for whether is scene is currently active by querying its * isActive property. We can test whether it has ended by testing * trainStop.ended != nil. If it is active we can look at its turnsActive * property to see how long it has been active for. * * The trainStop scene defined above will start when the player enters the * platform location, and will end when the 'dinner-date' tag is revealed * (perhaps as the result of some conversation that takes place on the * Platform). By default a Scene is only triggered once, so it won't recur * again the next time the player comes to the platform. If you want a * scene to recur, set its isRecurring property to true. To see how many * times a recurring scene has run, test its timesActive property (which * is only updated after the scene has ended, so this will be 1 for a * Scene that's in the course of running for the second time). * * We can elaborate this simple Scene definition in a number of ways. Every * turn when the Scene is active its daemon() method is called, so we can * override this method to do anything we want to happen each turn the * scene is active. If we define the Scene to be an EventList as well, * then by default the daemon() method will cause its doScript() method, * so that all we need to do to make an EventList fire every turn a Scene * is active is add the appropriate kind of EventList to the class list of * the Scene and define its EventList property. * * We can also arrange for a scene to end in more than one way by returning * different non-nil values from its howEnded() method (these methods * could be numbers, single-quoted strings, enums or objects, depending * what best suits our purpose). The finishOff(howEnded) method can then * test its howEnded parameter to see how the scene ended and respond * accordingly. If we later want to test how a Scene ended we can inspect * the value of is ended property (which will store the value returned by * endsWhen). * * A more elaborate form of the trainStop scene incorporating these * additional features might look like this: * *. trainStop: Scene, ShuffledEventList *. [ *. new function() { *. laura.moveIntoForTravel(platform); *. "A young woman steps off the train and looks around *. uncertainly. "; *. }, *. *. {: laura.initiateConversation(lauraPlatformTalkingState, *. 'asking-the-time') *. } *. ] *. *. [ *. 'The train despatcher wanders up and down, raising his whistle *. to his lips a few times without actually blowing it. ', *. *. 'A couple of passengers run down the platform and leap aboard *. the train. ', *. *. 'The loudspeaker announces that the 10.15 to London has been *. delayed -- yet again. ', *. *. 'Several doors slam on the train. ' *. ] *. *. startsWhen = (gPlayerChar.isIn(platform)) *. *. endsWhen() *. { *. if(gRevealed('dinner-date')) *. return 'romantically'; *. *. if(gRevealed('faithful-wife')) *. return 'sadly-but-wisely'; *. *. return nil; *. } *. *. startUp() *. { *. train.moveInto(platform); *. "A train pulls into the station and pulls up at the platform. "; *. } *. *. finishOff(howEnded) *. { *. train.moveInto(nil); *. "The train starts moving and accelerates down the track. "; *. switch(howEnded) *. { *. case 'romantically': *. "You set off together for a nearby restaurant..."; *. gPlayerChar.moveIntoForTravel(restaurant); *. laura.moveIntoForTravel(restaurant); *. break; *. case 'sadly-but-wisely': *. "You watch sadly as Laura turns and walks away. "; *. laura.moveInto(nil); *. break; *. } *. } *. ; * * Another way of defining alternative endings is to make the ending types * objects and define the finishOff code on those objects. In this case * we'd define the endsWhen method like this: * *. endsWhen() *. { *. if(gRevealed('dinner-date')) *. return romanticEnding; *. *. if(gRevealed('faithful-wife')) *. return sadButWiseEnding; *. *. return nil; *. } *. * Then perhaps define the finishOff(howEnded) method thus: *. *. finishOff(howEnded) *. { *. train.moveInto(nil); *. "The train starts moving and accelerates down the track. "; *. inherited(howEnded); // NOTE THIS CALL TO THE INHERITED METHOD *. } * * And finally define our two ending type objects: *. *. romanticEnding: object *. finishOff() *. { *. "You set off together for a nearby restaurant..."; *. gPlayerChar.moveIntoForTravel(restaurant); *. laura.moveIntoForTravel(restaurant); *. } *. ; *. *. sadButWiseEnding: object *. finishOff() *. { *. "You watch sadly as Laura turns and walks away. "; *. laura.moveInto(nil); *. } *. ; * * Yet another alternative would be to start another scene based on how * this scene ended, for example: * *. romanticDinnerScene: Scene *. startsWhen = (trainStop.ended == 'romantically') *. *. startUp() *. { *. You set off together for a nearby restaurant..."; *. gPlayerChar.moveIntoForTravel(restaurant); *. laura.moveIntoForTravel(restaurant); *. } *. ... *. ; */ ModuleID name = 'Scenes' byLine = 'by Eric Eve' htmlByLine 'by Eric Eve' version = '1.0' listingOrder = 70 ; /* * A DaemonControl is a utility class that slightly simplifies the task of * starting and stopping a basic Daemon */ class DaemonControl: object daemonID = nil /* * If startDaemon is called without any arguments, it will start up a * regular Daemon called every turn. If an argument is supplied it * should be a positive integer indicating the interval between * successive invocations of the daemon. */ startDaemon([args]) { local turns = (args.length == 0 ? 1 : args[1]); if(daemonID == nil) daemonID = new Daemon(self, &daemon, turns); } /* Stop this Daemon and remove it from the event list */ stopDaemon() { if(daemonID != nil) { stoppingDaemon(); daemonID.removeEvent(); daemonID = nil; } } /* * The daemon method is called each turn the Daemon is running (or * each Nth turn if startDaemon() was called as startDaemon(N) ). * * By default we call our doScript() method if we're a Script or * EventList. */ daemon() { if(ofKind(Script)) doScript(); } /* * The stoppingDaemon is called just before the Daemon is stopped, via * a call to stopDaemon(). By default it does nothing, but we can add * our own code here. */ stoppingDaemon() { } ; /* * a Scene is an abstract object that we can use to mark developments in * the plot of our game. Any number of Scenes can be active at once, but * the transition from one Scene to another can be used to mark * significant developments in ous Story. * * To determine whether a particular Scene is currently active, test its * isActive property. * * * Use the startsWhen and endsWhen methods to set the conditions under * which this Scene starts and finishes. * * Override the startUp() method to control what happens when this Scene * starts. * * Override the finishOff(howEnded) method to control what happens when * this Scene ends. The howEnded parameter is the value returned from the * endsEhen method and can be used to make the Scene end in different * ways, if we wish. * * Every turn when the Scene is active its daemon() method will be * executed; we can use this for anything we want to happen each turn the * Scene is active, or we can define the Scene to be some kind of EventList * and everyTurn() will drive it. */ class Scene: DaemonControl /* is this scene currently active? */ isActive = nil /* The number of turns this Scene has been active */ turnsActive = (libGlobal.totalTurns - startedWhen) /* Make this scene active */ activate() { /* Note that we're now active */ isActive = true; /* * Note when we started. We subtract one from the turn count since * the turn count will have advanced by one by the time we're * called. */ startedWhen = libGlobal.totalTurns - 1; /* Start our associated Daemon */ startDaemon(daemonInterval); /* Call our custom startup code */ startUp(); } /* Override this method to define what happens when this scene starts */ startUp() { } /* * The frequency with which our daemon() method is called when we're * active. */ daemonInterval = 1 /* * End this scene. This can take single parameter that can simply be * true (if the scene just ends with no further specification) or it * can be some kind of value (e.g. an enum, a string, or an object) * indicating how the scene ended; e.g. well or badly. If this argument * is not supplied, a value of true is assumed. */ deactivate(...) { local howEnded = argcount > 0 ? getArg(1) : true; /* Mark us no longer active */ isActive = nil; /* Call our custom finishing off code */ finishOff(howEnded); /* Add to the count of the number of times we've been active */ timesActive++; /* Note how we ended */ ended = howEnded; /* Stop my associated Daemon */ stopDaemon(); /* If I'm not needed again, remove me from the list */ if(!isRecurring) sceneController.sceneList.removeElement(self); } /* * Override this method to define what happens when this scene ends. * This can optionally depend on how the scene ended, as defined by the * how parameter. * * A neat scheme for providing alternative endings might be to make the * how parameter an object and have the finishOff(howEnded) method call a * method on the howEnded object. This is implemented as the default, * provided the howEnded parameter is passed as an object; otherwise the * default behaviour is to do nothing. */ finishOff(howEnded) { if(dataType(howEnded) == TypeObject) howEnded.finishOff(); } /* * Override this method with the condition that must be true for this * scene to start. */ startsWhen { return nil; } /* * Override this method with the condition that must obtain for this * scene to end. If this scene can end in more this way, return a value * (e.g. an enum, object or string) indicating how the scene ended, * otherwise just return true. */ endsWhen { return nil; } /* * Is this a recurring scene? If so, then it will start up each time * startsWhen becomes true. Otherwise (the default) it will start up * only the first time. */ isRecurring = nil /* The number of times this scene has been active */ timesActive = 0 /* * The turn count when this scene last started. This can be used, e.g., * if we want a scene to last a set number of turns. */ startedWhen = 0 /* * A record of how this scene last ended, if it can end in more than one * way. A value of nil means this scene has never ended. A value of * true means it just ended. Any other value indicates a particular * kind of ending. */ ended = nil ; /* Build a list of scenes at Preinit */ PreinitObject execute() { forEachInstance(Scene, {x: sceneController.sceneList.append(x) }); } ; /* Watch for the opening and closing of scenes each turn */ sceneController: InitObject execute() { new PromptDaemon(self, &sceneCheck); } /* The list of all scenes in the game */ sceneList = static new Vector(10) sceneCheck() { local how = nil; foreach(local cur in sceneList) { /* Check if any active scenes are ready to be ended */ if(cur.isActive && ((how = cur.endsWhen) != nil)) cur.deactivate(how); /* Check if any non-active scenes are due for activation */ if(!cur.isActive && cur.startsWhen && (cur.isRecurring || cur.timesActive == 0)) cur.activate(); } } ;