| #charset "us-ascii" | |
| #include <adv3.h> | |
| #include <en_us.h> | |
| /* | |
| * SCENES: A Scene Control Extension | |
| *. by Eric Eve | |
| *. Version 1.1; 11-Mar-25 | |
| * | |
| * 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 <a href="mailto:eric.eve@hmc.ox.ac.uk">Eric Eve</a>' | |
| version = '1.1' | |
| 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 */ | |
| scenesPreinit: 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(); | |
| } | |
| } | |
| ; |
Xet Storage Details
- Size:
- 15.5 kB
- Xet hash:
- af30e886f03325e5a041b6e8187ede82ae28f5f0d5c7eb9b6cd415763536ed60
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.