#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();
}
;