#charset "us-ascii"

#include <adv3.h>
#include <en_us.h>


/* 
 *   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 <a href="mailto:eric.eve@hmc.ox.ac.uk">Eric Eve</a>'
    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();               
               
        }
    }
;
