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

#ifndef LANGHEADER
   #define LANGHEADER <LANGUAGE.h>
#endif
#include LANGHEADER

/*
*   Reaction objects extension.
*
*   This creates BeforeAction and AfterAction objects, and a framework to use
*   them, rather like AskTellTopic. The idea is that instead of filling your
*   beforeAction and afterAction methods with sphaghetti code, all you have to
*   do is define a set of BeforeAction and AfterAction objects and nest them in
*   the objects that needs to react to things.
*
*   The main two types of Reaction objects are BeforeAction and AfterAction.
*   These can be nested in any kind of Thing in your game world, as well as in
*   any ActorState. The extension also provides RoomBeforeAction and
*   RoomAfterAction objects, which can be used with any Room (including
*   NestedRooms).
*
*   All Reaction objects provide the same set of properties. actor, action,
*   dobj, and iobj allow you to decide when the Reaction should happen. If they
*   are defined, they must match the current action before the Reaction will
*   run. They can be either a single object or a list of objects. If you use a
*   list, matching any of the items in the list will allow the Reaction to run.
*   You can also define a condition, which lets you add any extra restrictions
*   you want.
*
*   If isOneOff is true, the reaction will only happen once; otherwise it will
*   happen every time it is active. If isDone is true, the reaction will no
*   longer run. If isOneOff is true, isDone will be automatically set when the
*   reaction first runs, but you can also set it manually if you want to. If
*   stopAction is true, the reaction will exit() the current action.
*
*   When a Reaction is active, it will run the code in its react() method.
*   Alternatively, you can create a Reaction that also inherits from an
*   EventList class; in that case, the EventList will run when the Reaction is
*   active.
*
*   You can determine the order Reactions happen in by changing their priority.
*   The higher the priority, the sooner the reaction will happen. The default
*   is 100.
*
*   Some example code:
*
*   linda: Person
*     'linda'
*     'Linda'
*     isProperName = true
*     isHer = true
*     isMad = nil
*   ;
*
*   + AfterAction
*      actor = gPlayerChar
*      action = ExamineAction
*      dobj = linda
*      isOneOff = true
*      react() {
*        "Linda catches your eye. <q>Hey, quit starin' at me!</q> she snaps. ";
*        linda.isMad = true;
*      }
*   ;
*
*   + AfterAction
*     actor = gPlayerChar
*     action = ExamineAction
*     dobj = linda
*     condition = (linda.isMad)
*     isOneOff = true
*     priority = 150  // runs earlier than the other AfterActions
*     react() {
*       "You don't even see Linda's fist coming. Flat on your back, you stare
*       up at her in confusion.<.p>
*       <q>I told you to keep your sneaky eyes off me!</q> she says, and
*       stalks out of the room. ";
*       linda.moveIntoForTravel(nil);
*       gPlayerChar.hasHeadache = true;
*     }
*
*   + BeforeAction
*     actor = gPlayerChar
*     action = TakeAction
*     dobj = stackOfCash
*     stopAction = true
*     react() {
*       "Linda slaps your hand away. <q>Hands off!</q> ";
*     }
*   ;
*
*   This extension is open source software licensed under the MIT Licence:
*
*   Copyright (c) 2013 Emily Boegheim
*
*   Permission is hereby granted, free of charge, to any person obtaining a
*   copy of this software and associated documentation files (the "Software"),
*   to deal in the Software without restriction, including without limitation
*   the rights to use, copy, modify, merge, publish, distribute, sublicense,
*   and/or sell copies of the Software, and to permit persons to whom the
*   Software is furnished to do so, subject to the following conditions:
*
*   The above copyright notice and this permission notice shall be included in
*   all copies or substantial portions of the Software.
*
*   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
*   DEALINGS IN THE SOFTWARE.
*
*   Change history:
*
*   0.2
*   - changed priority ordering from lowest-first to highest-first and made the
*     default priority 100 to match similar properties in adv3
*   - simplified handling of the "condition" property
*   - fixed a remarkably stupid bug where I'd done all the work to implement
*     RoomBeforeAction and RoomAfterAction classes except, y'know, actually
*     *implement* those classes
*   - changed Reaction.isActive to always return nil if isDone is true
*     (previously this only worked if isOneOff was also true, but authors
*     might want to set isDone manually on other kinds of Reactions as well)
*   - added credits module
*
*   0.1
*   - first public beta release
*/


/* Info on the extension, so it will show up in the list of credits. */
reactionsModuleID: ModuleID
   name = 'Reactions'
   byline = 'by Emily Boegheim'
   htmlByline = 'by <a href="mailto:[email protected]">Emily
       Boegheim</a>'
   version = '0.2'
;


/*
*   First the base class: Reaction. Provides default properties and isActive
*   handling.
*/
class Reaction: object
   actor = nil
   action = nil
   dobj = nil
   iobj = nil
   condition = true

   /* some reactions should only happen once */
   isOneOff = nil
   isDone = nil

   /*
    *   Which list should this Reaction belong to? This needs to be overridden
    *   for each subclass. Authors using the extension shouldn't need to change
    *   this unless they are creating their own Reaction subclasses.
    */
   reactionObjList = nil

   /*
    *   Check to see if the Reaction is active. By default, a Reaction is
    *   active when, if any of its action-matching properties (actor, action,
    *   dobj, iobj) have a value, those defined match those in existence. The
    *   properties can be either lists (in which case any of the items in the
    *   list may match) or a single object/class (in which case it must match
    *   specifically).
    *
    *   This class also provides a "condition" property, which must evaluate
    *   to true.
    *
    *   For more complex conditions, authors may simply override this function
    *   to return whatever result they wish.
    */
   isActive()
   {
       /* not if it's over and done with */
       if (isDone)
           return nil;

       /* check the conditions */
       if (actor != nil && ((actor.ofKind(List) && !actor.indexOf(gActor))
                            || (actor.ofKind(Thing) && actor != gActor)))
           return nil;
       if (action != nil && ((action.ofKind(List) && !actionInList()) ||
                             (action.ofKind(Action) && !gAction.ofKind(action))))
           return nil;
       if (dobj != nil && ((dobj.ofKind(List) && !dobj.indexOf(gDobj))
                            || (dobj.ofKind(Thing) && dobj != gDobj)))
           return nil;
       if (iobj != nil && ((iobj.ofKind(List) && !iobj.indexOf(gIobj))
                            || (iobj.ofKind(Thing) && iobj != gIobj)))
           return nil;
       return condition;
   }

   /* Check whether the current action is in this object's action list. */
   actionInList()
   {
       foreach (local cur in action)
       {
           if (gAction.ofKind(cur))
               return true;
       }
       return nil;
   }

   /*
    *   If this property is set to true, the action will be stopped after
    *   this object has performed its reaction.
    */
   stopAction = nil

   /*
    *   Assuming we're active, react. This method should not be overridden
    *   by authors, as it does a little housekeeping.
    */
   exec()
   {
       /*
        *   If we inherit from Script, run the script; otherwise let the
        *   react() method handle it. Stop the action afterwards if
        *   necessary.
        */
       if (ofKind(Script))
           doScript();
       else
           react();

       /* If this is a one-off reaction, note that it's done. */
       if (isOneOff)
           isDone = true;

       /* If this reaction stops the action, stop it now. */
       if (stopAction)
           exit;
   }

   /* A method for authors to override - called by exec(). */
   react()
   {
       /* by default, do nothing */
   }

   /*
    *   We may want some objects to react before others, so a priority
    *   property. The lower the priority, the sooner this object will be
    *   called. The default is 100, for no reason other than to be consistent
    *   with adv3's handling of similar properties.
    */
   priority = 100

   /*
    *   For consistency with adv3 classes, a method to return the actor to
    *   which this ReAction object belongs.
    */
   getActor()
   {
       /* if nested in an ActorState or something similar */
       if (location.ofKind(ActorState))
           return location.getActor();

       /* otherwise just return whatever it's nested in */
       return location;
   }
;


/* The Reaction subclasses. */
class BeforeAction: Reaction
   reactionObjList = &beforeObjList
;

class AfterAction: Reaction
   reactionObjList = &afterObjList
;

class RoomBeforeAction: Reaction
   reactionObjList = &roomBeforeObjList
;

class RoomAfterAction: Reaction
   reactionObjList = &roomAfterObjList
;


/*
*   Preinitialisation must make sure each object in the game gets lists of
*   its BeforeAction and AfterAction objects.
*/
reactionObjsPreinit: PreinitObject
   /*
    *   Run through all the Reaction objects in the game and place them in the
    *   correct list in their parent objects. Then sort the lists.
    */
   execute()
   {
       /* Add all objects descended from Reaction to their owners' lists. */
       forEachInstance(Reaction, new function(x) {
           local prop = x.reactionObjList();
           x.location.(prop) = x.location.(prop).append(x);
       });

       /* Sort the lists. */
       local func = {a, b: a.priority - b.priority};
       for (local obj = firstObj(Thing); obj != nil;
               obj = nextObj(obj, Thing)) {
           sortLists(obj, func);
       }
       for (local obj = firstObj(ActorState); obj != nil;
               obj = nextObj(obj, ActorState)) {
           sortLists(obj, func);
       }
   }

   sortLists(obj, func)
   {
       /* All the lists that need to be sorted. */
       local lists = [&beforeObjList, &afterObjList];
       if (obj.ofKind(BasicLocation))
       {
           lists += [&roomBeforeObjList, &roomAfterObjList];
       }

       /* Sort each of the lists for the given object. */
       foreach (local l in lists)
       {
           if (obj.(l).length > 0)
               obj.(l) = obj.(l).sort(true, func);
       }
   }

   execBeforeMe = [adv3LibPreinit]
;


/* Now to teach objects how to use their brand-spanking new Reaction objects. */
modify Thing
   /*
    *   The lists of BeforeAction or AfterAction objects. None by default,
    *   of course.
    */
   beforeObjList = []
   afterObjList = []

   /*
    *   The beforeAction method must check whether this object has any
    *   BeforeAction objects, and if so, whether any of them are active. Tell
    *   any active ones to react.
    */
   beforeAction()
   {
       foreach (local obj in beforeObjList) {
           if (obj.isActive)
               obj.exec();
       }
   }

   /* Same deal for afterAction. */
   afterAction()
   {
       foreach (local obj in afterObjList) {
           if (obj.isActive)
               obj.exec();
       }
   }
;

modify BasicLocation
   /* The lists of RoomBeforeAction and RoomAfterAction objects. */
   roomBeforeObjList = []
   roomAfterObjList = []

   /*
    *   The roomBeforeAction method must check whether this object has any
    *   RoomBeforeAction objects, and if so, whether any of them are active.
    *   Tell any active ones to react.
    */
   roomBeforeAction()
   {
       foreach (local obj in roomBeforeObjList) {
           if (obj.isActive)
               obj.exec();
       }
   }

   /* Same deal for roomAfterAction. */
   roomAfterAction()
   {
       foreach (local obj in roomAfterObjList) {
           if (obj.isActive)
               obj.exec();
       }
   }
;


/*
*   Actors have special beforeAction handling, which we need to preserve
*   while still including the default Thing handling. Rather arbitrarily,
*   we'll let the Actor handling (including checking the current
*   ActorState's handling) run before the Thing handling.
*/
modify Actor
   beforeAction()
   {
       inherited();
       inherited Thing();
   }

   afterAction()
   {
       inherited();
       inherited Thing();
   }
;


/*
*   ActorStates should treat beforeAction and afterAction the same way Things
*   do.
*/
modify ActorState
   /*
    *   The lists of BeforeAction or AfterAction objects. None by default,
    *   of course.
    */
   beforeObjList = []
   afterObjList = []

   /*
    *   Delegate beforeAction and afterAction to the equivalent methods of
    *   Thing.
    */
   beforeAction()
   {
       delegated Thing();
   }

   afterAction()
   {
       delegated Thing();
   }
;


/*
*   Keyrings have special beforeAction and afterAction handling, which we
*   need to preserve while still including the default Thing handling.
*/
modify Keyring
   beforeAction()
   {
       inherited();
       inherited Thing();
   }

   afterAction()
   {
       inherited();
       inherited Thing();
   }
;