| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | #include <adv3.h> |
| | 4 | #include <en_us.h> |
| | 5 | |
| | 6 | /* |
| | 7 | * SIMPLE ATTACHABLE |
| | 8 | * |
| | 9 | * SimpleAttachable version 1.0 by Eric Eve |
| | 10 | * |
| | 11 | * This file defines the SimpleAttachable class together with some |
| | 12 | * supporting objects. Feel free to use this in your own games if you find |
| | 13 | * it useful. |
| | 14 | * |
| | 15 | * Attachables in general are complicated to handle, because they can |
| | 16 | * behave in so many different ways. The SimpleAttachable class is meant |
| | 17 | * to make handling one common case easier, in particular the case where a |
| | 18 | * smaller object is attached to a larger object and then moves round with |
| | 19 | * it. |
| | 20 | * |
| | 21 | * More formally, a SimpleAttachable enforces the following rules: |
| | 22 | * |
| | 23 | * (1) In any attachment relationship between SimpleAttachables, one |
| | 24 | * object must be the major attachment, and all the others will be that |
| | 25 | * object's minor attachments (if there's a fridge with a red magnet and a |
| | 26 | * blue magnet attached, the fridge is the major attachement and the |
| | 27 | * magnets are its minor attachments). |
| | 28 | * |
| | 29 | * (2) A major attachment can have many minor attachments attached to |
| | 30 | * it at once, but a minor attachment can only be attached to one major |
| | 31 | * attachment at a time (this is a consequence of (3) below). |
| | 32 | * |
| | 33 | * (3) When a minor attachment is attached to a major attachment, the |
| | 34 | * minor attachment is moved into the major attachment. This automatically |
| | 35 | * enforces (4) below. |
| | 36 | * |
| | 37 | * (4) When a major attachment is moved (e.g. by being taken or pushed |
| | 38 | * around), its minor attachments automatically move with it. |
| | 39 | * |
| | 40 | * (5) When a minor attachment is taken, it is automatically detached |
| | 41 | * from its major attachment (if I take a magnet, I leave the fridge |
| | 42 | * behind). |
| | 43 | * |
| | 44 | * (6) When a minor attachment is detached from a major attachment it |
| | 45 | * is moved into the major attachment's location. |
| | 46 | * |
| | 47 | * (7) The same SimpleAttachable can be simultaneously a minor item |
| | 48 | * for one object and a major item for one or more other objects (we could |
| | 49 | * attach a metal paper clip to the magnet while the magnet is attached to |
| | 50 | * the fridge; if we take the magnet the paper clip comes with it while the |
| | 51 | * fridge is left behind). |
| | 52 | * |
| | 53 | * (8) If a SimpleAttachable is attached to a major attachment while |
| | 54 | * it's already attached to another major attachment, it will first be |
| | 55 | * detached from its existing major attachment before being attached to |
| | 56 | * the new one (ATTACH MAGNET TO OVEN will trigger an implicit DETACH |
| | 57 | * MAGNET FROM FRIDGE if the magnet was attached to the fridge). |
| | 58 | * |
| | 59 | * (9) Normally, both the major and the minor attachments should be of |
| | 60 | * class SimpleAttachable. |
| | 61 | * |
| | 62 | * |
| | 63 | * Setting up a SimpleAttachable is then straightforward, since all the |
| | 64 | * complications are handled on the class. In the simplest case all the |
| | 65 | * game author needs to do is to define the minorAttachmentItems property |
| | 66 | * on the major SimpleAttachable to hold a list of items that can be |
| | 67 | * attached to it, e.g.: |
| | 68 | * |
| | 69 | * minorAttachmentItems = [redMagnet, blueMagnet] |
| | 70 | * |
| | 71 | * If a more complex way of deciding what can be attached to a major |
| | 72 | * SimpleAttachable is required, override its isMajorItemFor() method |
| | 73 | * instead, so that it returns true for any obj that can be attached, e.g.: |
| | 74 | * |
| | 75 | * isMajorItemFor(obj) { return obj.ofKind(Magnet); } |
| | 76 | * |
| | 77 | * One further point to note: if you want a Container-type object to act |
| | 78 | * as a major SimpleAttachment, you'll need to make it a ComplexContainer. |
| | 79 | * |
| | 80 | */ |
| | 81 | |
| | 82 | ModuleID |
| | 83 | name = 'SimpleAttachable' |
| | 84 | byLine = 'by Eric Eve' |
| | 85 | htmlByLine = 'by <A href="mailto:eric.eve@hmc.ox.ac.uk">Eric Eve</a>' |
| | 86 | version = '1.0' |
| | 87 | ; |
| | 88 | |
| | 89 | |
| | 90 | class SimpleAttachable: Attachable |
| | 91 | |
| | 92 | /* Move the minor attachment into the major attachment. */ |
| | 93 | handleAttach(other) |
| | 94 | { |
| | 95 | if(other.isMajorItemFor(self)) |
| | 96 | moveInto(other); |
| | 97 | |
| | 98 | } |
| | 99 | |
| | 100 | /* |
| | 101 | * When we're detached, if we were in the other object move us into the |
| | 102 | * other object's location. |
| | 103 | */ |
| | 104 | handleDetach(other) |
| | 105 | { |
| | 106 | if(isIn(other)) |
| | 107 | moveInto(other.location); |
| | 108 | } |
| | 109 | |
| | 110 | /* |
| | 111 | * If a minor attachment is taken, first detach it from its major |
| | 112 | * attachment. |
| | 113 | */ |
| | 114 | dobjFor(Take) |
| | 115 | { |
| | 116 | preCond = (nilToList(inherited) + objNotAttachedToMajor) |
| | 117 | } |
| | 118 | |
| | 119 | |
| | 120 | /* |
| | 121 | * If we're attached to a major attachment, treat TAKE US FROM MAJOR as |
| | 122 | * equivalent to DETACH US FROM MAJOR. |
| | 123 | */ |
| | 124 | dobjFor(TakeFrom) maybeRemapTo(isAttachedToMajor, DetachFrom, self, |
| | 125 | location) |
| | 126 | |
| | 127 | /* |
| | 128 | * If we're already attached to a major attachment, detach us from it |
| | 129 | * before attaching us to a different major atttachment. |
| | 130 | */ |
| | 131 | dobjFor(AttachTo) |
| | 132 | { |
| | 133 | preCond = (nilToList(inherited) + objDetachedFromLocation) |
| | 134 | } |
| | 135 | |
| | 136 | iobjFor(AttachTo) |
| | 137 | { |
| | 138 | preCond = (nilToList(inherited) + objDetachedFromLocation) |
| | 139 | } |
| | 140 | |
| | 141 | /* We're a major item for any item in our minorAttachmentItems list. */ |
| | 142 | isMajorItemFor(obj) |
| | 143 | { |
| | 144 | return nilToList(minorAttachmentItems).indexOf(obj) != nil; |
| | 145 | } |
| | 146 | |
| | 147 | /* |
| | 148 | * The list of items that can be attached to us for which we would be |
| | 149 | * the major attachment item. |
| | 150 | */ |
| | 151 | minorAttachmentItems = [] |
| | 152 | |
| | 153 | /* |
| | 154 | * A pair of convenience methods to determined if we're attached to any |
| | 155 | * items that are major or minor attachments relative to us. |
| | 156 | */ |
| | 157 | isAttachedToMajor = (location && location.isMajorItemFor(self)) |
| | 158 | isAttachedToMinor = (contents.indexWhich({x: self.isMajorItemFor(x)}) != |
| | 159 | nil) |
| | 160 | |
| | 161 | /* |
| | 162 | * Define if this item be listed when it's a minor item attached to |
| | 163 | * another item. |
| | 164 | */ |
| | 165 | isListedWhenAttached = true |
| | 166 | |
| | 167 | isListed = (isAttachedToMajor ? isListedWhenAttached : inherited ) |
| | 168 | |
| | 169 | isListedInContents = (isAttachedToMajor ? nil : inherited ) |
| | 170 | |
| | 171 | /* |
| | 172 | * Customise the listers so that if we contain minor items as |
| | 173 | * attachments they're shows as being attached to us, not as being in |
| | 174 | * us. |
| | 175 | */ |
| | 176 | contentsLister = (isAttachedToMinor ? majorAttachmentLister : inherited) |
| | 177 | |
| | 178 | inlineContentsLister = (isAttachedToMinor ? inlineListingAttachmentsLister : |
| | 179 | inherited ) |
| | 180 | |
| | 181 | |
| | 182 | /* |
| | 183 | * A SimpleAttachment can be attached to another SimpleAttachment if |
| | 184 | * one of the SimpleAttachments is a major item for the other. |
| | 185 | */ |
| | 186 | canAttachTo(obj) |
| | 187 | { |
| | 188 | return isMajorItemFor(obj) || obj.isMajorItemFor(self); |
| | 189 | } |
| | 190 | |
| | 191 | |
| | 192 | /* |
| | 193 | * If I start the game located in an object that's a major item for me, |
| | 194 | * presumbably we're meant to start off attached. |
| | 195 | */ |
| | 196 | initializeThing() |
| | 197 | { |
| | 198 | inherited; |
| | 199 | |
| | 200 | if(location && location.isMajorItemFor(self)) |
| | 201 | attachTo(location); |
| | 202 | } |
| | 203 | ; |
| | 204 | |
| | 205 | /* |
| | 206 | * Custom lister to show the contents of a major attachment as being |
| | 207 | * attached to it. |
| | 208 | */ |
| | 209 | inlineListingAttachmentsLister: ContentsLister |
| | 210 | showListEmpty(pov, parent) { } |
| | 211 | showListPrefixWide(cnt, pov, parent) |
| | 212 | { " (to which <<cnt > 1 ? '{are|were}' : '{is|was}'>> attached "; } |
| | 213 | showListSuffixWide(itemCount, pov, parent) |
| | 214 | { ")"; } |
| | 215 | ; |
| | 216 | |
| | 217 | /* Special precondition for use when taking a minor attachment. */ |
| | 218 | |
| | 219 | objNotAttachedToMajor: PreCondition |
| | 220 | |
| | 221 | /* |
| | 222 | * Other things being equal, prefer to take an item that's not a minor |
| | 223 | * attachment (if the blue magnet is attached to the fridge and the red |
| | 224 | * magnet is lying on the floor, then make TAKE MAGNET take the red |
| | 225 | * one). |
| | 226 | */ |
| | 227 | verifyPreCondition(obj) |
| | 228 | { |
| | 229 | if(obj.location.isMajorItemFor(obj)) |
| | 230 | logicalRank(90, 'attached'); |
| | 231 | } |
| | 232 | |
| | 233 | |
| | 234 | checkPreCondition(obj, allowImplicit) |
| | 235 | { |
| | 236 | /* |
| | 237 | * if we don't already have any non-permanent attachments that |
| | 238 | * are the major attachments for us, we're fine (as we don't |
| | 239 | * require removing permanent attachments); nothing more needs to |
| | 240 | * be done. |
| | 241 | */ |
| | 242 | if (obj.attachedObjects.indexWhich( |
| | 243 | {x: x.isMajorItemFor(obj) && !obj.isPermanentlyAttachedTo(x) |
| | 244 | }) == nil) |
| | 245 | return nil; |
| | 246 | |
| | 247 | |
| | 248 | local major = obj.attachedObjects.valWhich({x: x.isMajorItemFor(obj)}); |
| | 249 | |
| | 250 | /* |
| | 251 | * Try implicitly detaching us from our major attachment. |
| | 252 | */ |
| | 253 | if (allowImplicit && tryImplicitAction(DetachFrom, obj, major)) |
| | 254 | { |
| | 255 | /* |
| | 256 | * if we're still attached to a major attachment, we failed, |
| | 257 | * so abort |
| | 258 | */ |
| | 259 | if (obj.attachedObjects.indexWhich( |
| | 260 | {x: !obj.isPermanentlyAttachedTo(x) |
| | 261 | && x.isMajorItemFor(obj)}) != nil) |
| | 262 | exit; |
| | 263 | |
| | 264 | /* tell the caller we executed an implied action */ |
| | 265 | return true; |
| | 266 | } |
| | 267 | |
| | 268 | /* we must detach first */ |
| | 269 | reportFailure(&mustDetachMsg, obj); |
| | 270 | exit; |
| | 271 | } |
| | 272 | ; |
| | 273 | |
| | 274 | /* |
| | 275 | * Special precondition to detach us from an existing major attachment |
| | 276 | * before attaching us to another one. This needs to be different from |
| | 277 | * objNotAttachedToMajor so that we don't perform any unnecessary |
| | 278 | * detachments. |
| | 279 | */ |
| | 280 | |
| | 281 | objDetachedFromLocation: PreCondition |
| | 282 | checkPreCondition(obj, allowImplicit) |
| | 283 | { |
| | 284 | /* |
| | 285 | * If the other object involved in the command is not a majorItem |
| | 286 | * for us, or we're not already in an object that we're attached |
| | 287 | * to which is our major item, then there's nothing to do. |
| | 288 | */ |
| | 289 | |
| | 290 | local other = (obj == gDobj ? gIobj : gDobj); |
| | 291 | local loc = obj.location; |
| | 292 | |
| | 293 | if(!other.isMajorItemFor(obj) || |
| | 294 | !(loc.isMajorItemFor(obj) && obj.isAttachedTo(loc))) |
| | 295 | return nil; |
| | 296 | |
| | 297 | |
| | 298 | |
| | 299 | /* |
| | 300 | * if we don't already have any non-permanent attachments that |
| | 301 | * are the major attachments for us, we're fine (as we don't |
| | 302 | * require removing permanent attachments); nothing more needs to |
| | 303 | * be done. |
| | 304 | */ |
| | 305 | if (allowImplicit && tryImplicitAction(DetachFrom, obj, loc)) |
| | 306 | { |
| | 307 | /* if we're still attached to anything, we failed, so abort */ |
| | 308 | if (loc.isMajorItemFor(obj) && obj.isAttachedTo(loc)) |
| | 309 | exit; |
| | 310 | |
| | 311 | /* tell the caller we executed an implied action */ |
| | 312 | return true; |
| | 313 | } |
| | 314 | |
| | 315 | /* we must detach first */ |
| | 316 | reportFailure(&mustDetachMsg, obj); |
| | 317 | exit; |
| | 318 | } |
| | 319 | |
| | 320 | ; |