#include #include #ifndef LANGHEADER #define LANGHEADER #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. Hey, quit starin' at me! 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> * I told you to keep your sneaky eyes off me! 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. Hands off! "; * } * ; * * 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 Emily Boegheim' 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(); } ;